##// END OF EJS Templates
merge with stable
Matt Mackall -
r15489:25ea33fe merge default
parent child Browse files
Show More
@@ -1,725 +1,725 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid
8 from node import nullid
9 from i18n import _
9 from i18n import _
10 import scmutil, util, ignore, osutil, parsers, encoding
10 import scmutil, util, ignore, osutil, parsers, encoding
11 import struct, os, stat, errno
11 import struct, os, stat, errno
12 import cStringIO
12 import cStringIO
13
13
14 _format = ">cllll"
14 _format = ">cllll"
15 propertycache = util.propertycache
15 propertycache = util.propertycache
16
16
17 def _finddirs(path):
17 def _finddirs(path):
18 pos = path.rfind('/')
18 pos = path.rfind('/')
19 while pos != -1:
19 while pos != -1:
20 yield path[:pos]
20 yield path[:pos]
21 pos = path.rfind('/', 0, pos)
21 pos = path.rfind('/', 0, pos)
22
22
23 def _incdirs(dirs, path):
23 def _incdirs(dirs, path):
24 for base in _finddirs(path):
24 for base in _finddirs(path):
25 if base in dirs:
25 if base in dirs:
26 dirs[base] += 1
26 dirs[base] += 1
27 return
27 return
28 dirs[base] = 1
28 dirs[base] = 1
29
29
30 def _decdirs(dirs, path):
30 def _decdirs(dirs, path):
31 for base in _finddirs(path):
31 for base in _finddirs(path):
32 if dirs[base] > 1:
32 if dirs[base] > 1:
33 dirs[base] -= 1
33 dirs[base] -= 1
34 return
34 return
35 del dirs[base]
35 del dirs[base]
36
36
37 class dirstate(object):
37 class dirstate(object):
38
38
39 def __init__(self, opener, ui, root, validate):
39 def __init__(self, opener, ui, root, validate):
40 '''Create a new dirstate object.
40 '''Create a new dirstate object.
41
41
42 opener is an open()-like callable that can be used to open the
42 opener is an open()-like callable that can be used to open the
43 dirstate file; root is the root of the directory tracked by
43 dirstate file; root is the root of the directory tracked by
44 the dirstate.
44 the dirstate.
45 '''
45 '''
46 self._opener = opener
46 self._opener = opener
47 self._validate = validate
47 self._validate = validate
48 self._root = root
48 self._root = root
49 self._rootdir = os.path.join(root, '')
49 self._rootdir = os.path.join(root, '')
50 self._dirty = False
50 self._dirty = False
51 self._dirtypl = False
51 self._dirtypl = False
52 self._lastnormaltime = None
52 self._lastnormaltime = None
53 self._ui = ui
53 self._ui = ui
54
54
55 @propertycache
55 @propertycache
56 def _map(self):
56 def _map(self):
57 '''Return the dirstate contents as a map from filename to
57 '''Return the dirstate contents as a map from filename to
58 (state, mode, size, time).'''
58 (state, mode, size, time).'''
59 self._read()
59 self._read()
60 return self._map
60 return self._map
61
61
62 @propertycache
62 @propertycache
63 def _copymap(self):
63 def _copymap(self):
64 self._read()
64 self._read()
65 return self._copymap
65 return self._copymap
66
66
67 @propertycache
67 @propertycache
68 def _foldmap(self):
68 def _foldmap(self):
69 f = {}
69 f = {}
70 for name in self._map:
70 for name in self._map:
71 f[os.path.normcase(name)] = name
71 f[os.path.normcase(name)] = name
72 return f
72 return f
73
73
74 @propertycache
74 @propertycache
75 def _branch(self):
75 def _branch(self):
76 try:
76 try:
77 return self._opener.read("branch").strip() or "default"
77 return self._opener.read("branch").strip() or "default"
78 except IOError:
78 except IOError:
79 return "default"
79 return "default"
80
80
81 @propertycache
81 @propertycache
82 def _pl(self):
82 def _pl(self):
83 try:
83 try:
84 fp = self._opener("dirstate")
84 fp = self._opener("dirstate")
85 st = fp.read(40)
85 st = fp.read(40)
86 fp.close()
86 fp.close()
87 l = len(st)
87 l = len(st)
88 if l == 40:
88 if l == 40:
89 return st[:20], st[20:40]
89 return st[:20], st[20:40]
90 elif l > 0 and l < 40:
90 elif l > 0 and l < 40:
91 raise util.Abort(_('working directory state appears damaged!'))
91 raise util.Abort(_('working directory state appears damaged!'))
92 except IOError, err:
92 except IOError, err:
93 if err.errno != errno.ENOENT:
93 if err.errno != errno.ENOENT:
94 raise
94 raise
95 return [nullid, nullid]
95 return [nullid, nullid]
96
96
97 @propertycache
97 @propertycache
98 def _dirs(self):
98 def _dirs(self):
99 dirs = {}
99 dirs = {}
100 for f, s in self._map.iteritems():
100 for f, s in self._map.iteritems():
101 if s[0] != 'r':
101 if s[0] != 'r':
102 _incdirs(dirs, f)
102 _incdirs(dirs, f)
103 return dirs
103 return dirs
104
104
105 @propertycache
105 @propertycache
106 def _ignore(self):
106 def _ignore(self):
107 files = [self._join('.hgignore')]
107 files = [self._join('.hgignore')]
108 for name, path in self._ui.configitems("ui"):
108 for name, path in self._ui.configitems("ui"):
109 if name == 'ignore' or name.startswith('ignore.'):
109 if name == 'ignore' or name.startswith('ignore.'):
110 files.append(util.expandpath(path))
110 files.append(util.expandpath(path))
111 return ignore.ignore(self._root, files, self._ui.warn)
111 return ignore.ignore(self._root, files, self._ui.warn)
112
112
113 @propertycache
113 @propertycache
114 def _slash(self):
114 def _slash(self):
115 return self._ui.configbool('ui', 'slash') and os.sep != '/'
115 return self._ui.configbool('ui', 'slash') and os.sep != '/'
116
116
117 @propertycache
117 @propertycache
118 def _checklink(self):
118 def _checklink(self):
119 return util.checklink(self._root)
119 return util.checklink(self._root)
120
120
121 @propertycache
121 @propertycache
122 def _checkexec(self):
122 def _checkexec(self):
123 return util.checkexec(self._root)
123 return util.checkexec(self._root)
124
124
125 @propertycache
125 @propertycache
126 def _checkcase(self):
126 def _checkcase(self):
127 return not util.checkcase(self._join('.hg'))
127 return not util.checkcase(self._join('.hg'))
128
128
129 def _join(self, f):
129 def _join(self, f):
130 # much faster than os.path.join()
130 # much faster than os.path.join()
131 # it's safe because f is always a relative path
131 # it's safe because f is always a relative path
132 return self._rootdir + f
132 return self._rootdir + f
133
133
134 def flagfunc(self, buildfallback):
134 def flagfunc(self, buildfallback):
135 if self._checklink and self._checkexec:
135 if self._checklink and self._checkexec:
136 def f(x):
136 def f(x):
137 p = self._join(x)
137 p = self._join(x)
138 if os.path.islink(p):
138 if os.path.islink(p):
139 return 'l'
139 return 'l'
140 if util.isexec(p):
140 if util.isexec(p):
141 return 'x'
141 return 'x'
142 return ''
142 return ''
143 return f
143 return f
144
144
145 fallback = buildfallback()
145 fallback = buildfallback()
146 if self._checklink:
146 if self._checklink:
147 def f(x):
147 def f(x):
148 if os.path.islink(self._join(x)):
148 if os.path.islink(self._join(x)):
149 return 'l'
149 return 'l'
150 if 'x' in fallback(x):
150 if 'x' in fallback(x):
151 return 'x'
151 return 'x'
152 return ''
152 return ''
153 return f
153 return f
154 if self._checkexec:
154 if self._checkexec:
155 def f(x):
155 def f(x):
156 if 'l' in fallback(x):
156 if 'l' in fallback(x):
157 return 'l'
157 return 'l'
158 if util.isexec(self._join(x)):
158 if util.isexec(self._join(x)):
159 return 'x'
159 return 'x'
160 return ''
160 return ''
161 return f
161 return f
162 else:
162 else:
163 return fallback
163 return fallback
164
164
165 def getcwd(self):
165 def getcwd(self):
166 cwd = os.getcwd()
166 cwd = os.getcwd()
167 if cwd == self._root:
167 if cwd == self._root:
168 return ''
168 return ''
169 # self._root ends with a path separator if self._root is '/' or 'C:\'
169 # self._root ends with a path separator if self._root is '/' or 'C:\'
170 rootsep = self._root
170 rootsep = self._root
171 if not util.endswithsep(rootsep):
171 if not util.endswithsep(rootsep):
172 rootsep += os.sep
172 rootsep += os.sep
173 if cwd.startswith(rootsep):
173 if cwd.startswith(rootsep):
174 return cwd[len(rootsep):]
174 return cwd[len(rootsep):]
175 else:
175 else:
176 # we're outside the repo. return an absolute path.
176 # we're outside the repo. return an absolute path.
177 return cwd
177 return cwd
178
178
179 def pathto(self, f, cwd=None):
179 def pathto(self, f, cwd=None):
180 if cwd is None:
180 if cwd is None:
181 cwd = self.getcwd()
181 cwd = self.getcwd()
182 path = util.pathto(self._root, cwd, f)
182 path = util.pathto(self._root, cwd, f)
183 if self._slash:
183 if self._slash:
184 return util.normpath(path)
184 return util.normpath(path)
185 return path
185 return path
186
186
187 def __getitem__(self, key):
187 def __getitem__(self, key):
188 '''Return the current state of key (a filename) in the dirstate.
188 '''Return the current state of key (a filename) in the dirstate.
189
189
190 States are:
190 States are:
191 n normal
191 n normal
192 m needs merging
192 m needs merging
193 r marked for removal
193 r marked for removal
194 a marked for addition
194 a marked for addition
195 ? not tracked
195 ? not tracked
196 '''
196 '''
197 return self._map.get(key, ("?",))[0]
197 return self._map.get(key, ("?",))[0]
198
198
199 def __contains__(self, key):
199 def __contains__(self, key):
200 return key in self._map
200 return key in self._map
201
201
202 def __iter__(self):
202 def __iter__(self):
203 for x in sorted(self._map):
203 for x in sorted(self._map):
204 yield x
204 yield x
205
205
206 def parents(self):
206 def parents(self):
207 return [self._validate(p) for p in self._pl]
207 return [self._validate(p) for p in self._pl]
208
208
209 def p1(self):
209 def p1(self):
210 return self._validate(self._pl[0])
210 return self._validate(self._pl[0])
211
211
212 def p2(self):
212 def p2(self):
213 return self._validate(self._pl[1])
213 return self._validate(self._pl[1])
214
214
215 def branch(self):
215 def branch(self):
216 return encoding.tolocal(self._branch)
216 return encoding.tolocal(self._branch)
217
217
218 def setparents(self, p1, p2=nullid):
218 def setparents(self, p1, p2=nullid):
219 self._dirty = self._dirtypl = True
219 self._dirty = self._dirtypl = True
220 self._pl = p1, p2
220 self._pl = p1, p2
221
221
222 def setbranch(self, branch):
222 def setbranch(self, branch):
223 if branch in ['tip', '.', 'null']:
223 if branch in ['tip', '.', 'null']:
224 raise util.Abort(_('the name \'%s\' is reserved') % branch)
224 raise util.Abort(_('the name \'%s\' is reserved') % branch)
225 self._branch = encoding.fromlocal(branch)
225 self._branch = encoding.fromlocal(branch)
226 self._opener.write("branch", self._branch + '\n')
226 self._opener.write("branch", self._branch + '\n')
227
227
228 def _read(self):
228 def _read(self):
229 self._map = {}
229 self._map = {}
230 self._copymap = {}
230 self._copymap = {}
231 try:
231 try:
232 st = self._opener.read("dirstate")
232 st = self._opener.read("dirstate")
233 except IOError, err:
233 except IOError, err:
234 if err.errno != errno.ENOENT:
234 if err.errno != errno.ENOENT:
235 raise
235 raise
236 return
236 return
237 if not st:
237 if not st:
238 return
238 return
239
239
240 p = parsers.parse_dirstate(self._map, self._copymap, st)
240 p = parsers.parse_dirstate(self._map, self._copymap, st)
241 if not self._dirtypl:
241 if not self._dirtypl:
242 self._pl = p
242 self._pl = p
243
243
244 def invalidate(self):
244 def invalidate(self):
245 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
245 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
246 "_ignore"):
246 "_ignore"):
247 if a in self.__dict__:
247 if a in self.__dict__:
248 delattr(self, a)
248 delattr(self, a)
249 self._lastnormaltime = None
249 self._lastnormaltime = None
250 self._dirty = False
250 self._dirty = False
251
251
252 def copy(self, source, dest):
252 def copy(self, source, dest):
253 """Mark dest as a copy of source. Unmark dest if source is None."""
253 """Mark dest as a copy of source. Unmark dest if source is None."""
254 if source == dest:
254 if source == dest:
255 return
255 return
256 self._dirty = True
256 self._dirty = True
257 if source is not None:
257 if source is not None:
258 self._copymap[dest] = source
258 self._copymap[dest] = source
259 elif dest in self._copymap:
259 elif dest in self._copymap:
260 del self._copymap[dest]
260 del self._copymap[dest]
261
261
262 def copied(self, file):
262 def copied(self, file):
263 return self._copymap.get(file, None)
263 return self._copymap.get(file, None)
264
264
265 def copies(self):
265 def copies(self):
266 return self._copymap
266 return self._copymap
267
267
268 def _droppath(self, f):
268 def _droppath(self, f):
269 if self[f] not in "?r" and "_dirs" in self.__dict__:
269 if self[f] not in "?r" and "_dirs" in self.__dict__:
270 _decdirs(self._dirs, f)
270 _decdirs(self._dirs, f)
271
271
272 def _addpath(self, f, check=False):
272 def _addpath(self, f, check=False):
273 oldstate = self[f]
273 oldstate = self[f]
274 if check or oldstate == "r":
274 if check or oldstate == "r":
275 scmutil.checkfilename(f)
275 scmutil.checkfilename(f)
276 if f in self._dirs:
276 if f in self._dirs:
277 raise util.Abort(_('directory %r already in dirstate') % f)
277 raise util.Abort(_('directory %r already in dirstate') % f)
278 # shadows
278 # shadows
279 for d in _finddirs(f):
279 for d in _finddirs(f):
280 if d in self._dirs:
280 if d in self._dirs:
281 break
281 break
282 if d in self._map and self[d] != 'r':
282 if d in self._map and self[d] != 'r':
283 raise util.Abort(
283 raise util.Abort(
284 _('file %r in dirstate clashes with %r') % (d, f))
284 _('file %r in dirstate clashes with %r') % (d, f))
285 if oldstate in "?r" and "_dirs" in self.__dict__:
285 if oldstate in "?r" and "_dirs" in self.__dict__:
286 _incdirs(self._dirs, f)
286 _incdirs(self._dirs, f)
287
287
288 def normal(self, f):
288 def normal(self, f):
289 '''Mark a file normal and clean.'''
289 '''Mark a file normal and clean.'''
290 self._dirty = True
290 self._dirty = True
291 self._addpath(f)
291 self._addpath(f)
292 s = os.lstat(self._join(f))
292 s = os.lstat(self._join(f))
293 mtime = int(s.st_mtime)
293 mtime = int(s.st_mtime)
294 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
294 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
295 if f in self._copymap:
295 if f in self._copymap:
296 del self._copymap[f]
296 del self._copymap[f]
297 if mtime > self._lastnormaltime:
297 if mtime > self._lastnormaltime:
298 # Remember the most recent modification timeslot for status(),
298 # Remember the most recent modification timeslot for status(),
299 # to make sure we won't miss future size-preserving file content
299 # to make sure we won't miss future size-preserving file content
300 # modifications that happen within the same timeslot.
300 # modifications that happen within the same timeslot.
301 self._lastnormaltime = mtime
301 self._lastnormaltime = mtime
302
302
303 def normallookup(self, f):
303 def normallookup(self, f):
304 '''Mark a file normal, but possibly dirty.'''
304 '''Mark a file normal, but possibly dirty.'''
305 if self._pl[1] != nullid and f in self._map:
305 if self._pl[1] != nullid and f in self._map:
306 # if there is a merge going on and the file was either
306 # if there is a merge going on and the file was either
307 # in state 'm' (-1) or coming from other parent (-2) before
307 # in state 'm' (-1) or coming from other parent (-2) before
308 # being removed, restore that state.
308 # being removed, restore that state.
309 entry = self._map[f]
309 entry = self._map[f]
310 if entry[0] == 'r' and entry[2] in (-1, -2):
310 if entry[0] == 'r' and entry[2] in (-1, -2):
311 source = self._copymap.get(f)
311 source = self._copymap.get(f)
312 if entry[2] == -1:
312 if entry[2] == -1:
313 self.merge(f)
313 self.merge(f)
314 elif entry[2] == -2:
314 elif entry[2] == -2:
315 self.otherparent(f)
315 self.otherparent(f)
316 if source:
316 if source:
317 self.copy(source, f)
317 self.copy(source, f)
318 return
318 return
319 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
319 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
320 return
320 return
321 self._dirty = True
321 self._dirty = True
322 self._addpath(f)
322 self._addpath(f)
323 self._map[f] = ('n', 0, -1, -1)
323 self._map[f] = ('n', 0, -1, -1)
324 if f in self._copymap:
324 if f in self._copymap:
325 del self._copymap[f]
325 del self._copymap[f]
326
326
327 def otherparent(self, f):
327 def otherparent(self, f):
328 '''Mark as coming from the other parent, always dirty.'''
328 '''Mark as coming from the other parent, always dirty.'''
329 if self._pl[1] == nullid:
329 if self._pl[1] == nullid:
330 raise util.Abort(_("setting %r to other parent "
330 raise util.Abort(_("setting %r to other parent "
331 "only allowed in merges") % f)
331 "only allowed in merges") % f)
332 self._dirty = True
332 self._dirty = True
333 self._addpath(f)
333 self._addpath(f)
334 self._map[f] = ('n', 0, -2, -1)
334 self._map[f] = ('n', 0, -2, -1)
335 if f in self._copymap:
335 if f in self._copymap:
336 del self._copymap[f]
336 del self._copymap[f]
337
337
338 def add(self, f):
338 def add(self, f):
339 '''Mark a file added.'''
339 '''Mark a file added.'''
340 self._dirty = True
340 self._dirty = True
341 self._addpath(f, True)
341 self._addpath(f, True)
342 self._map[f] = ('a', 0, -1, -1)
342 self._map[f] = ('a', 0, -1, -1)
343 if f in self._copymap:
343 if f in self._copymap:
344 del self._copymap[f]
344 del self._copymap[f]
345
345
346 def remove(self, f):
346 def remove(self, f):
347 '''Mark a file removed.'''
347 '''Mark a file removed.'''
348 self._dirty = True
348 self._dirty = True
349 self._droppath(f)
349 self._droppath(f)
350 size = 0
350 size = 0
351 if self._pl[1] != nullid and f in self._map:
351 if self._pl[1] != nullid and f in self._map:
352 # backup the previous state
352 # backup the previous state
353 entry = self._map[f]
353 entry = self._map[f]
354 if entry[0] == 'm': # merge
354 if entry[0] == 'm': # merge
355 size = -1
355 size = -1
356 elif entry[0] == 'n' and entry[2] == -2: # other parent
356 elif entry[0] == 'n' and entry[2] == -2: # other parent
357 size = -2
357 size = -2
358 self._map[f] = ('r', 0, size, 0)
358 self._map[f] = ('r', 0, size, 0)
359 if size == 0 and f in self._copymap:
359 if size == 0 and f in self._copymap:
360 del self._copymap[f]
360 del self._copymap[f]
361
361
362 def merge(self, f):
362 def merge(self, f):
363 '''Mark a file merged.'''
363 '''Mark a file merged.'''
364 self._dirty = True
364 self._dirty = True
365 s = os.lstat(self._join(f))
365 s = os.lstat(self._join(f))
366 self._addpath(f)
366 self._addpath(f)
367 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
367 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
368 if f in self._copymap:
368 if f in self._copymap:
369 del self._copymap[f]
369 del self._copymap[f]
370
370
371 def drop(self, f):
371 def drop(self, f):
372 '''Drop a file from the dirstate'''
372 '''Drop a file from the dirstate'''
373 if f in self._map:
373 if f in self._map:
374 self._dirty = True
374 self._dirty = True
375 self._droppath(f)
375 self._droppath(f)
376 del self._map[f]
376 del self._map[f]
377
377
378 def _normalize(self, path, isknown):
378 def _normalize(self, path, isknown):
379 normed = os.path.normcase(path)
379 normed = util.normcase(path)
380 folded = self._foldmap.get(normed, None)
380 folded = self._foldmap.get(normed, None)
381 if folded is None:
381 if folded is None:
382 if isknown or not os.path.lexists(os.path.join(self._root, path)):
382 if isknown or not os.path.lexists(os.path.join(self._root, path)):
383 folded = path
383 folded = path
384 else:
384 else:
385 folded = self._foldmap.setdefault(normed,
385 folded = self._foldmap.setdefault(normed,
386 util.fspath(path, self._root))
386 util.fspath(path, self._root))
387 return folded
387 return folded
388
388
389 def normalize(self, path, isknown=False):
389 def normalize(self, path, isknown=False):
390 '''
390 '''
391 normalize the case of a pathname when on a casefolding filesystem
391 normalize the case of a pathname when on a casefolding filesystem
392
392
393 isknown specifies whether the filename came from walking the
393 isknown specifies whether the filename came from walking the
394 disk, to avoid extra filesystem access
394 disk, to avoid extra filesystem access
395
395
396 The normalized case is determined based on the following precedence:
396 The normalized case is determined based on the following precedence:
397
397
398 - version of name already stored in the dirstate
398 - version of name already stored in the dirstate
399 - version of name stored on disk
399 - version of name stored on disk
400 - version provided via command arguments
400 - version provided via command arguments
401 '''
401 '''
402
402
403 if self._checkcase:
403 if self._checkcase:
404 return self._normalize(path, isknown)
404 return self._normalize(path, isknown)
405 return path
405 return path
406
406
407 def clear(self):
407 def clear(self):
408 self._map = {}
408 self._map = {}
409 if "_dirs" in self.__dict__:
409 if "_dirs" in self.__dict__:
410 delattr(self, "_dirs")
410 delattr(self, "_dirs")
411 self._copymap = {}
411 self._copymap = {}
412 self._pl = [nullid, nullid]
412 self._pl = [nullid, nullid]
413 self._lastnormaltime = None
413 self._lastnormaltime = None
414 self._dirty = True
414 self._dirty = True
415
415
416 def rebuild(self, parent, files):
416 def rebuild(self, parent, files):
417 self.clear()
417 self.clear()
418 for f in files:
418 for f in files:
419 if 'x' in files.flags(f):
419 if 'x' in files.flags(f):
420 self._map[f] = ('n', 0777, -1, 0)
420 self._map[f] = ('n', 0777, -1, 0)
421 else:
421 else:
422 self._map[f] = ('n', 0666, -1, 0)
422 self._map[f] = ('n', 0666, -1, 0)
423 self._pl = (parent, nullid)
423 self._pl = (parent, nullid)
424 self._dirty = True
424 self._dirty = True
425
425
426 def write(self):
426 def write(self):
427 if not self._dirty:
427 if not self._dirty:
428 return
428 return
429 st = self._opener("dirstate", "w", atomictemp=True)
429 st = self._opener("dirstate", "w", atomictemp=True)
430
430
431 # use the modification time of the newly created temporary file as the
431 # use the modification time of the newly created temporary file as the
432 # filesystem's notion of 'now'
432 # filesystem's notion of 'now'
433 now = int(util.fstat(st).st_mtime)
433 now = int(util.fstat(st).st_mtime)
434
434
435 cs = cStringIO.StringIO()
435 cs = cStringIO.StringIO()
436 copymap = self._copymap
436 copymap = self._copymap
437 pack = struct.pack
437 pack = struct.pack
438 write = cs.write
438 write = cs.write
439 write("".join(self._pl))
439 write("".join(self._pl))
440 for f, e in self._map.iteritems():
440 for f, e in self._map.iteritems():
441 if e[0] == 'n' and e[3] == now:
441 if e[0] == 'n' and e[3] == now:
442 # The file was last modified "simultaneously" with the current
442 # The file was last modified "simultaneously" with the current
443 # write to dirstate (i.e. within the same second for file-
443 # write to dirstate (i.e. within the same second for file-
444 # systems with a granularity of 1 sec). This commonly happens
444 # systems with a granularity of 1 sec). This commonly happens
445 # for at least a couple of files on 'update'.
445 # for at least a couple of files on 'update'.
446 # The user could change the file without changing its size
446 # The user could change the file without changing its size
447 # within the same second. Invalidate the file's stat data in
447 # within the same second. Invalidate the file's stat data in
448 # dirstate, forcing future 'status' calls to compare the
448 # dirstate, forcing future 'status' calls to compare the
449 # contents of the file. This prevents mistakenly treating such
449 # contents of the file. This prevents mistakenly treating such
450 # files as clean.
450 # files as clean.
451 e = (e[0], 0, -1, -1) # mark entry as 'unset'
451 e = (e[0], 0, -1, -1) # mark entry as 'unset'
452 self._map[f] = e
452 self._map[f] = e
453
453
454 if f in copymap:
454 if f in copymap:
455 f = "%s\0%s" % (f, copymap[f])
455 f = "%s\0%s" % (f, copymap[f])
456 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
456 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
457 write(e)
457 write(e)
458 write(f)
458 write(f)
459 st.write(cs.getvalue())
459 st.write(cs.getvalue())
460 st.close()
460 st.close()
461 self._lastnormaltime = None
461 self._lastnormaltime = None
462 self._dirty = self._dirtypl = False
462 self._dirty = self._dirtypl = False
463
463
464 def _dirignore(self, f):
464 def _dirignore(self, f):
465 if f == '.':
465 if f == '.':
466 return False
466 return False
467 if self._ignore(f):
467 if self._ignore(f):
468 return True
468 return True
469 for p in _finddirs(f):
469 for p in _finddirs(f):
470 if self._ignore(p):
470 if self._ignore(p):
471 return True
471 return True
472 return False
472 return False
473
473
474 def walk(self, match, subrepos, unknown, ignored):
474 def walk(self, match, subrepos, unknown, ignored):
475 '''
475 '''
476 Walk recursively through the directory tree, finding all files
476 Walk recursively through the directory tree, finding all files
477 matched by match.
477 matched by match.
478
478
479 Return a dict mapping filename to stat-like object (either
479 Return a dict mapping filename to stat-like object (either
480 mercurial.osutil.stat instance or return value of os.stat()).
480 mercurial.osutil.stat instance or return value of os.stat()).
481 '''
481 '''
482
482
483 def fwarn(f, msg):
483 def fwarn(f, msg):
484 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
484 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
485 return False
485 return False
486
486
487 def badtype(mode):
487 def badtype(mode):
488 kind = _('unknown')
488 kind = _('unknown')
489 if stat.S_ISCHR(mode):
489 if stat.S_ISCHR(mode):
490 kind = _('character device')
490 kind = _('character device')
491 elif stat.S_ISBLK(mode):
491 elif stat.S_ISBLK(mode):
492 kind = _('block device')
492 kind = _('block device')
493 elif stat.S_ISFIFO(mode):
493 elif stat.S_ISFIFO(mode):
494 kind = _('fifo')
494 kind = _('fifo')
495 elif stat.S_ISSOCK(mode):
495 elif stat.S_ISSOCK(mode):
496 kind = _('socket')
496 kind = _('socket')
497 elif stat.S_ISDIR(mode):
497 elif stat.S_ISDIR(mode):
498 kind = _('directory')
498 kind = _('directory')
499 return _('unsupported file type (type is %s)') % kind
499 return _('unsupported file type (type is %s)') % kind
500
500
501 ignore = self._ignore
501 ignore = self._ignore
502 dirignore = self._dirignore
502 dirignore = self._dirignore
503 if ignored:
503 if ignored:
504 ignore = util.never
504 ignore = util.never
505 dirignore = util.never
505 dirignore = util.never
506 elif not unknown:
506 elif not unknown:
507 # if unknown and ignored are False, skip step 2
507 # if unknown and ignored are False, skip step 2
508 ignore = util.always
508 ignore = util.always
509 dirignore = util.always
509 dirignore = util.always
510
510
511 matchfn = match.matchfn
511 matchfn = match.matchfn
512 badfn = match.bad
512 badfn = match.bad
513 dmap = self._map
513 dmap = self._map
514 normpath = util.normpath
514 normpath = util.normpath
515 listdir = osutil.listdir
515 listdir = osutil.listdir
516 lstat = os.lstat
516 lstat = os.lstat
517 getkind = stat.S_IFMT
517 getkind = stat.S_IFMT
518 dirkind = stat.S_IFDIR
518 dirkind = stat.S_IFDIR
519 regkind = stat.S_IFREG
519 regkind = stat.S_IFREG
520 lnkkind = stat.S_IFLNK
520 lnkkind = stat.S_IFLNK
521 join = self._join
521 join = self._join
522 work = []
522 work = []
523 wadd = work.append
523 wadd = work.append
524
524
525 exact = skipstep3 = False
525 exact = skipstep3 = False
526 if matchfn == match.exact: # match.exact
526 if matchfn == match.exact: # match.exact
527 exact = True
527 exact = True
528 dirignore = util.always # skip step 2
528 dirignore = util.always # skip step 2
529 elif match.files() and not match.anypats(): # match.match, no patterns
529 elif match.files() and not match.anypats(): # match.match, no patterns
530 skipstep3 = True
530 skipstep3 = True
531
531
532 if self._checkcase:
532 if self._checkcase:
533 normalize = self._normalize
533 normalize = self._normalize
534 skipstep3 = False
534 skipstep3 = False
535 else:
535 else:
536 normalize = lambda x, y: x
536 normalize = lambda x, y: x
537
537
538 files = sorted(match.files())
538 files = sorted(match.files())
539 subrepos.sort()
539 subrepos.sort()
540 i, j = 0, 0
540 i, j = 0, 0
541 while i < len(files) and j < len(subrepos):
541 while i < len(files) and j < len(subrepos):
542 subpath = subrepos[j] + "/"
542 subpath = subrepos[j] + "/"
543 if files[i] < subpath:
543 if files[i] < subpath:
544 i += 1
544 i += 1
545 continue
545 continue
546 while i < len(files) and files[i].startswith(subpath):
546 while i < len(files) and files[i].startswith(subpath):
547 del files[i]
547 del files[i]
548 j += 1
548 j += 1
549
549
550 if not files or '.' in files:
550 if not files or '.' in files:
551 files = ['']
551 files = ['']
552 results = dict.fromkeys(subrepos)
552 results = dict.fromkeys(subrepos)
553 results['.hg'] = None
553 results['.hg'] = None
554
554
555 # step 1: find all explicit files
555 # step 1: find all explicit files
556 for ff in files:
556 for ff in files:
557 nf = normalize(normpath(ff), False)
557 nf = normalize(normpath(ff), False)
558 if nf in results:
558 if nf in results:
559 continue
559 continue
560
560
561 try:
561 try:
562 st = lstat(join(nf))
562 st = lstat(join(nf))
563 kind = getkind(st.st_mode)
563 kind = getkind(st.st_mode)
564 if kind == dirkind:
564 if kind == dirkind:
565 skipstep3 = False
565 skipstep3 = False
566 if nf in dmap:
566 if nf in dmap:
567 #file deleted on disk but still in dirstate
567 #file deleted on disk but still in dirstate
568 results[nf] = None
568 results[nf] = None
569 match.dir(nf)
569 match.dir(nf)
570 if not dirignore(nf):
570 if not dirignore(nf):
571 wadd(nf)
571 wadd(nf)
572 elif kind == regkind or kind == lnkkind:
572 elif kind == regkind or kind == lnkkind:
573 results[nf] = st
573 results[nf] = st
574 else:
574 else:
575 badfn(ff, badtype(kind))
575 badfn(ff, badtype(kind))
576 if nf in dmap:
576 if nf in dmap:
577 results[nf] = None
577 results[nf] = None
578 except OSError, inst:
578 except OSError, inst:
579 if nf in dmap: # does it exactly match a file?
579 if nf in dmap: # does it exactly match a file?
580 results[nf] = None
580 results[nf] = None
581 else: # does it match a directory?
581 else: # does it match a directory?
582 prefix = nf + "/"
582 prefix = nf + "/"
583 for fn in dmap:
583 for fn in dmap:
584 if fn.startswith(prefix):
584 if fn.startswith(prefix):
585 match.dir(nf)
585 match.dir(nf)
586 skipstep3 = False
586 skipstep3 = False
587 break
587 break
588 else:
588 else:
589 badfn(ff, inst.strerror)
589 badfn(ff, inst.strerror)
590
590
591 # step 2: visit subdirectories
591 # step 2: visit subdirectories
592 while work:
592 while work:
593 nd = work.pop()
593 nd = work.pop()
594 skip = None
594 skip = None
595 if nd == '.':
595 if nd == '.':
596 nd = ''
596 nd = ''
597 else:
597 else:
598 skip = '.hg'
598 skip = '.hg'
599 try:
599 try:
600 entries = listdir(join(nd), stat=True, skip=skip)
600 entries = listdir(join(nd), stat=True, skip=skip)
601 except OSError, inst:
601 except OSError, inst:
602 if inst.errno == errno.EACCES:
602 if inst.errno == errno.EACCES:
603 fwarn(nd, inst.strerror)
603 fwarn(nd, inst.strerror)
604 continue
604 continue
605 raise
605 raise
606 for f, kind, st in entries:
606 for f, kind, st in entries:
607 nf = normalize(nd and (nd + "/" + f) or f, True)
607 nf = normalize(nd and (nd + "/" + f) or f, True)
608 if nf not in results:
608 if nf not in results:
609 if kind == dirkind:
609 if kind == dirkind:
610 if not ignore(nf):
610 if not ignore(nf):
611 match.dir(nf)
611 match.dir(nf)
612 wadd(nf)
612 wadd(nf)
613 if nf in dmap and matchfn(nf):
613 if nf in dmap and matchfn(nf):
614 results[nf] = None
614 results[nf] = None
615 elif kind == regkind or kind == lnkkind:
615 elif kind == regkind or kind == lnkkind:
616 if nf in dmap:
616 if nf in dmap:
617 if matchfn(nf):
617 if matchfn(nf):
618 results[nf] = st
618 results[nf] = st
619 elif matchfn(nf) and not ignore(nf):
619 elif matchfn(nf) and not ignore(nf):
620 results[nf] = st
620 results[nf] = st
621 elif nf in dmap and matchfn(nf):
621 elif nf in dmap and matchfn(nf):
622 results[nf] = None
622 results[nf] = None
623
623
624 # step 3: report unseen items in the dmap hash
624 # step 3: report unseen items in the dmap hash
625 if not skipstep3 and not exact:
625 if not skipstep3 and not exact:
626 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
626 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
627 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
627 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
628 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
628 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
629 st = None
629 st = None
630 results[nf] = st
630 results[nf] = st
631 for s in subrepos:
631 for s in subrepos:
632 del results[s]
632 del results[s]
633 del results['.hg']
633 del results['.hg']
634 return results
634 return results
635
635
636 def status(self, match, subrepos, ignored, clean, unknown):
636 def status(self, match, subrepos, ignored, clean, unknown):
637 '''Determine the status of the working copy relative to the
637 '''Determine the status of the working copy relative to the
638 dirstate and return a tuple of lists (unsure, modified, added,
638 dirstate and return a tuple of lists (unsure, modified, added,
639 removed, deleted, unknown, ignored, clean), where:
639 removed, deleted, unknown, ignored, clean), where:
640
640
641 unsure:
641 unsure:
642 files that might have been modified since the dirstate was
642 files that might have been modified since the dirstate was
643 written, but need to be read to be sure (size is the same
643 written, but need to be read to be sure (size is the same
644 but mtime differs)
644 but mtime differs)
645 modified:
645 modified:
646 files that have definitely been modified since the dirstate
646 files that have definitely been modified since the dirstate
647 was written (different size or mode)
647 was written (different size or mode)
648 added:
648 added:
649 files that have been explicitly added with hg add
649 files that have been explicitly added with hg add
650 removed:
650 removed:
651 files that have been explicitly removed with hg remove
651 files that have been explicitly removed with hg remove
652 deleted:
652 deleted:
653 files that have been deleted through other means ("missing")
653 files that have been deleted through other means ("missing")
654 unknown:
654 unknown:
655 files not in the dirstate that are not ignored
655 files not in the dirstate that are not ignored
656 ignored:
656 ignored:
657 files not in the dirstate that are ignored
657 files not in the dirstate that are ignored
658 (by _dirignore())
658 (by _dirignore())
659 clean:
659 clean:
660 files that have definitely not been modified since the
660 files that have definitely not been modified since the
661 dirstate was written
661 dirstate was written
662 '''
662 '''
663 listignored, listclean, listunknown = ignored, clean, unknown
663 listignored, listclean, listunknown = ignored, clean, unknown
664 lookup, modified, added, unknown, ignored = [], [], [], [], []
664 lookup, modified, added, unknown, ignored = [], [], [], [], []
665 removed, deleted, clean = [], [], []
665 removed, deleted, clean = [], [], []
666
666
667 dmap = self._map
667 dmap = self._map
668 ladd = lookup.append # aka "unsure"
668 ladd = lookup.append # aka "unsure"
669 madd = modified.append
669 madd = modified.append
670 aadd = added.append
670 aadd = added.append
671 uadd = unknown.append
671 uadd = unknown.append
672 iadd = ignored.append
672 iadd = ignored.append
673 radd = removed.append
673 radd = removed.append
674 dadd = deleted.append
674 dadd = deleted.append
675 cadd = clean.append
675 cadd = clean.append
676
676
677 lnkkind = stat.S_IFLNK
677 lnkkind = stat.S_IFLNK
678
678
679 for fn, st in self.walk(match, subrepos, listunknown,
679 for fn, st in self.walk(match, subrepos, listunknown,
680 listignored).iteritems():
680 listignored).iteritems():
681 if fn not in dmap:
681 if fn not in dmap:
682 if (listignored or match.exact(fn)) and self._dirignore(fn):
682 if (listignored or match.exact(fn)) and self._dirignore(fn):
683 if listignored:
683 if listignored:
684 iadd(fn)
684 iadd(fn)
685 elif listunknown:
685 elif listunknown:
686 uadd(fn)
686 uadd(fn)
687 continue
687 continue
688
688
689 state, mode, size, time = dmap[fn]
689 state, mode, size, time = dmap[fn]
690
690
691 if not st and state in "nma":
691 if not st and state in "nma":
692 dadd(fn)
692 dadd(fn)
693 elif state == 'n':
693 elif state == 'n':
694 # The "mode & lnkkind != lnkkind or self._checklink"
694 # The "mode & lnkkind != lnkkind or self._checklink"
695 # lines are an expansion of "islink => checklink"
695 # lines are an expansion of "islink => checklink"
696 # where islink means "is this a link?" and checklink
696 # where islink means "is this a link?" and checklink
697 # means "can we check links?".
697 # means "can we check links?".
698 mtime = int(st.st_mtime)
698 mtime = int(st.st_mtime)
699 if (size >= 0 and
699 if (size >= 0 and
700 (size != st.st_size
700 (size != st.st_size
701 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
701 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
702 and (mode & lnkkind != lnkkind or self._checklink)
702 and (mode & lnkkind != lnkkind or self._checklink)
703 or size == -2 # other parent
703 or size == -2 # other parent
704 or fn in self._copymap):
704 or fn in self._copymap):
705 madd(fn)
705 madd(fn)
706 elif (mtime != time
706 elif (mtime != time
707 and (mode & lnkkind != lnkkind or self._checklink)):
707 and (mode & lnkkind != lnkkind or self._checklink)):
708 ladd(fn)
708 ladd(fn)
709 elif mtime == self._lastnormaltime:
709 elif mtime == self._lastnormaltime:
710 # fn may have been changed in the same timeslot without
710 # fn may have been changed in the same timeslot without
711 # changing its size. This can happen if we quickly do
711 # changing its size. This can happen if we quickly do
712 # multiple commits in a single transaction.
712 # multiple commits in a single transaction.
713 # Force lookup, so we don't miss such a racy file change.
713 # Force lookup, so we don't miss such a racy file change.
714 ladd(fn)
714 ladd(fn)
715 elif listclean:
715 elif listclean:
716 cadd(fn)
716 cadd(fn)
717 elif state == 'm':
717 elif state == 'm':
718 madd(fn)
718 madd(fn)
719 elif state == 'a':
719 elif state == 'a':
720 aadd(fn)
720 aadd(fn)
721 elif state == 'r':
721 elif state == 'r':
722 radd(fn)
722 radd(fn)
723
723
724 return (lookup, modified, added, removed, deleted, unknown, ignored,
724 return (lookup, modified, added, removed, deleted, unknown, ignored,
725 clean)
725 clean)
@@ -1,394 +1,398 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import os, sys, errno, stat, getpass, pwd, grp, tempfile
9 import os, sys, errno, stat, getpass, pwd, grp, tempfile
10
10
11 posixfile = open
11 posixfile = open
12 nulldev = '/dev/null'
12 nulldev = '/dev/null'
13 normpath = os.path.normpath
13 normpath = os.path.normpath
14 samestat = os.path.samestat
14 samestat = os.path.samestat
15 oslink = os.link
15 oslink = os.link
16 unlink = os.unlink
16 unlink = os.unlink
17 rename = os.rename
17 rename = os.rename
18 expandglobs = False
18 expandglobs = False
19
19
20 umask = os.umask(0)
20 umask = os.umask(0)
21 os.umask(umask)
21 os.umask(umask)
22
22
23 def openhardlinks():
23 def openhardlinks():
24 '''return true if it is safe to hold open file handles to hardlinks'''
24 '''return true if it is safe to hold open file handles to hardlinks'''
25 return True
25 return True
26
26
27 def nlinks(name):
27 def nlinks(name):
28 '''return number of hardlinks for the given file'''
28 '''return number of hardlinks for the given file'''
29 return os.lstat(name).st_nlink
29 return os.lstat(name).st_nlink
30
30
31 def parsepatchoutput(output_line):
31 def parsepatchoutput(output_line):
32 """parses the output produced by patch and returns the filename"""
32 """parses the output produced by patch and returns the filename"""
33 pf = output_line[14:]
33 pf = output_line[14:]
34 if os.sys.platform == 'OpenVMS':
34 if os.sys.platform == 'OpenVMS':
35 if pf[0] == '`':
35 if pf[0] == '`':
36 pf = pf[1:-1] # Remove the quotes
36 pf = pf[1:-1] # Remove the quotes
37 else:
37 else:
38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
39 pf = pf[1:-1] # Remove the quotes
39 pf = pf[1:-1] # Remove the quotes
40 return pf
40 return pf
41
41
42 def sshargs(sshcmd, host, user, port):
42 def sshargs(sshcmd, host, user, port):
43 '''Build argument list for ssh'''
43 '''Build argument list for ssh'''
44 args = user and ("%s@%s" % (user, host)) or host
44 args = user and ("%s@%s" % (user, host)) or host
45 return port and ("%s -p %s" % (args, port)) or args
45 return port and ("%s -p %s" % (args, port)) or args
46
46
47 def isexec(f):
47 def isexec(f):
48 """check whether a file is executable"""
48 """check whether a file is executable"""
49 return (os.lstat(f).st_mode & 0100 != 0)
49 return (os.lstat(f).st_mode & 0100 != 0)
50
50
51 def setflags(f, l, x):
51 def setflags(f, l, x):
52 s = os.lstat(f).st_mode
52 s = os.lstat(f).st_mode
53 if l:
53 if l:
54 if not stat.S_ISLNK(s):
54 if not stat.S_ISLNK(s):
55 # switch file to link
55 # switch file to link
56 fp = open(f)
56 fp = open(f)
57 data = fp.read()
57 data = fp.read()
58 fp.close()
58 fp.close()
59 os.unlink(f)
59 os.unlink(f)
60 try:
60 try:
61 os.symlink(data, f)
61 os.symlink(data, f)
62 except OSError:
62 except OSError:
63 # failed to make a link, rewrite file
63 # failed to make a link, rewrite file
64 fp = open(f, "w")
64 fp = open(f, "w")
65 fp.write(data)
65 fp.write(data)
66 fp.close()
66 fp.close()
67 # no chmod needed at this point
67 # no chmod needed at this point
68 return
68 return
69 if stat.S_ISLNK(s):
69 if stat.S_ISLNK(s):
70 # switch link to file
70 # switch link to file
71 data = os.readlink(f)
71 data = os.readlink(f)
72 os.unlink(f)
72 os.unlink(f)
73 fp = open(f, "w")
73 fp = open(f, "w")
74 fp.write(data)
74 fp.write(data)
75 fp.close()
75 fp.close()
76 s = 0666 & ~umask # avoid restatting for chmod
76 s = 0666 & ~umask # avoid restatting for chmod
77
77
78 sx = s & 0100
78 sx = s & 0100
79 if x and not sx:
79 if x and not sx:
80 # Turn on +x for every +r bit when making a file executable
80 # Turn on +x for every +r bit when making a file executable
81 # and obey umask.
81 # and obey umask.
82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
83 elif not x and sx:
83 elif not x and sx:
84 # Turn off all +x bits
84 # Turn off all +x bits
85 os.chmod(f, s & 0666)
85 os.chmod(f, s & 0666)
86
86
87 def copymode(src, dst, mode=None):
87 def copymode(src, dst, mode=None):
88 '''Copy the file mode from the file at path src to dst.
88 '''Copy the file mode from the file at path src to dst.
89 If src doesn't exist, we're using mode instead. If mode is None, we're
89 If src doesn't exist, we're using mode instead. If mode is None, we're
90 using umask.'''
90 using umask.'''
91 try:
91 try:
92 st_mode = os.lstat(src).st_mode & 0777
92 st_mode = os.lstat(src).st_mode & 0777
93 except OSError, inst:
93 except OSError, inst:
94 if inst.errno != errno.ENOENT:
94 if inst.errno != errno.ENOENT:
95 raise
95 raise
96 st_mode = mode
96 st_mode = mode
97 if st_mode is None:
97 if st_mode is None:
98 st_mode = ~umask
98 st_mode = ~umask
99 st_mode &= 0666
99 st_mode &= 0666
100 os.chmod(dst, st_mode)
100 os.chmod(dst, st_mode)
101
101
102 def checkexec(path):
102 def checkexec(path):
103 """
103 """
104 Check whether the given path is on a filesystem with UNIX-like exec flags
104 Check whether the given path is on a filesystem with UNIX-like exec flags
105
105
106 Requires a directory (like /foo/.hg)
106 Requires a directory (like /foo/.hg)
107 """
107 """
108
108
109 # VFAT on some Linux versions can flip mode but it doesn't persist
109 # VFAT on some Linux versions can flip mode but it doesn't persist
110 # a FS remount. Frequently we can detect it if files are created
110 # a FS remount. Frequently we can detect it if files are created
111 # with exec bit on.
111 # with exec bit on.
112
112
113 try:
113 try:
114 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
114 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
115 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
115 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
116 try:
116 try:
117 os.close(fh)
117 os.close(fh)
118 m = os.stat(fn).st_mode & 0777
118 m = os.stat(fn).st_mode & 0777
119 new_file_has_exec = m & EXECFLAGS
119 new_file_has_exec = m & EXECFLAGS
120 os.chmod(fn, m ^ EXECFLAGS)
120 os.chmod(fn, m ^ EXECFLAGS)
121 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
121 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
122 finally:
122 finally:
123 os.unlink(fn)
123 os.unlink(fn)
124 except (IOError, OSError):
124 except (IOError, OSError):
125 # we don't care, the user probably won't be able to commit anyway
125 # we don't care, the user probably won't be able to commit anyway
126 return False
126 return False
127 return not (new_file_has_exec or exec_flags_cannot_flip)
127 return not (new_file_has_exec or exec_flags_cannot_flip)
128
128
129 def checklink(path):
129 def checklink(path):
130 """check whether the given path is on a symlink-capable filesystem"""
130 """check whether the given path is on a symlink-capable filesystem"""
131 # mktemp is not racy because symlink creation will fail if the
131 # mktemp is not racy because symlink creation will fail if the
132 # file already exists
132 # file already exists
133 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
133 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
134 try:
134 try:
135 os.symlink(".", name)
135 os.symlink(".", name)
136 os.unlink(name)
136 os.unlink(name)
137 return True
137 return True
138 except (OSError, AttributeError):
138 except (OSError, AttributeError):
139 return False
139 return False
140
140
141 def checkosfilename(path):
141 def checkosfilename(path):
142 '''Check that the base-relative path is a valid filename on this platform.
142 '''Check that the base-relative path is a valid filename on this platform.
143 Returns None if the path is ok, or a UI string describing the problem.'''
143 Returns None if the path is ok, or a UI string describing the problem.'''
144 pass # on posix platforms, every path is ok
144 pass # on posix platforms, every path is ok
145
145
146 def setbinary(fd):
146 def setbinary(fd):
147 pass
147 pass
148
148
149 def pconvert(path):
149 def pconvert(path):
150 return path
150 return path
151
151
152 def localpath(path):
152 def localpath(path):
153 return path
153 return path
154
154
155 def samefile(fpath1, fpath2):
155 def samefile(fpath1, fpath2):
156 """Returns whether path1 and path2 refer to the same file. This is only
156 """Returns whether path1 and path2 refer to the same file. This is only
157 guaranteed to work for files, not directories."""
157 guaranteed to work for files, not directories."""
158 return os.path.samefile(fpath1, fpath2)
158 return os.path.samefile(fpath1, fpath2)
159
159
160 def samedevice(fpath1, fpath2):
160 def samedevice(fpath1, fpath2):
161 """Returns whether fpath1 and fpath2 are on the same device. This is only
161 """Returns whether fpath1 and fpath2 are on the same device. This is only
162 guaranteed to work for files, not directories."""
162 guaranteed to work for files, not directories."""
163 st1 = os.lstat(fpath1)
163 st1 = os.lstat(fpath1)
164 st2 = os.lstat(fpath2)
164 st2 = os.lstat(fpath2)
165 return st1.st_dev == st2.st_dev
165 return st1.st_dev == st2.st_dev
166
166
167 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
168 def normcase(path):
169 return path.lower()
170
167 if sys.platform == 'darwin':
171 if sys.platform == 'darwin':
168 import fcntl # only needed on darwin, missing on jython
172 import fcntl # only needed on darwin, missing on jython
169 def realpath(path):
173 def realpath(path):
170 '''
174 '''
171 Returns the true, canonical file system path equivalent to the given
175 Returns the true, canonical file system path equivalent to the given
172 path.
176 path.
173
177
174 Equivalent means, in this case, resulting in the same, unique
178 Equivalent means, in this case, resulting in the same, unique
175 file system link to the path. Every file system entry, whether a file,
179 file system link to the path. Every file system entry, whether a file,
176 directory, hard link or symbolic link or special, will have a single
180 directory, hard link or symbolic link or special, will have a single
177 path preferred by the system, but may allow multiple, differing path
181 path preferred by the system, but may allow multiple, differing path
178 lookups to point to it.
182 lookups to point to it.
179
183
180 Most regular UNIX file systems only allow a file system entry to be
184 Most regular UNIX file systems only allow a file system entry to be
181 looked up by its distinct path. Obviously, this does not apply to case
185 looked up by its distinct path. Obviously, this does not apply to case
182 insensitive file systems, whether case preserving or not. The most
186 insensitive file systems, whether case preserving or not. The most
183 complex issue to deal with is file systems transparently reencoding the
187 complex issue to deal with is file systems transparently reencoding the
184 path, such as the non-standard Unicode normalisation required for HFS+
188 path, such as the non-standard Unicode normalisation required for HFS+
185 and HFSX.
189 and HFSX.
186 '''
190 '''
187 # Constants copied from /usr/include/sys/fcntl.h
191 # Constants copied from /usr/include/sys/fcntl.h
188 F_GETPATH = 50
192 F_GETPATH = 50
189 O_SYMLINK = 0x200000
193 O_SYMLINK = 0x200000
190
194
191 try:
195 try:
192 fd = os.open(path, O_SYMLINK)
196 fd = os.open(path, O_SYMLINK)
193 except OSError, err:
197 except OSError, err:
194 if err.errno == errno.ENOENT:
198 if err.errno == errno.ENOENT:
195 return path
199 return path
196 raise
200 raise
197
201
198 try:
202 try:
199 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
203 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
200 finally:
204 finally:
201 os.close(fd)
205 os.close(fd)
202 elif sys.version_info < (2, 4, 2, 'final'):
206 elif sys.version_info < (2, 4, 2, 'final'):
203 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
207 # Workaround for http://bugs.python.org/issue1213894 (os.path.realpath
204 # didn't resolve symlinks that were the first component of the path.)
208 # didn't resolve symlinks that were the first component of the path.)
205 def realpath(path):
209 def realpath(path):
206 if os.path.isabs(path):
210 if os.path.isabs(path):
207 return os.path.realpath(path)
211 return os.path.realpath(path)
208 else:
212 else:
209 return os.path.realpath('./' + path)
213 return os.path.realpath('./' + path)
210 else:
214 else:
211 # Fallback to the likely inadequate Python builtin function.
215 # Fallback to the likely inadequate Python builtin function.
212 realpath = os.path.realpath
216 realpath = os.path.realpath
213
217
214 def shellquote(s):
218 def shellquote(s):
215 if os.sys.platform == 'OpenVMS':
219 if os.sys.platform == 'OpenVMS':
216 return '"%s"' % s
220 return '"%s"' % s
217 else:
221 else:
218 return "'%s'" % s.replace("'", "'\\''")
222 return "'%s'" % s.replace("'", "'\\''")
219
223
220 def quotecommand(cmd):
224 def quotecommand(cmd):
221 return cmd
225 return cmd
222
226
223 def popen(command, mode='r'):
227 def popen(command, mode='r'):
224 return os.popen(command, mode)
228 return os.popen(command, mode)
225
229
226 def testpid(pid):
230 def testpid(pid):
227 '''return False if pid dead, True if running or not sure'''
231 '''return False if pid dead, True if running or not sure'''
228 if os.sys.platform == 'OpenVMS':
232 if os.sys.platform == 'OpenVMS':
229 return True
233 return True
230 try:
234 try:
231 os.kill(pid, 0)
235 os.kill(pid, 0)
232 return True
236 return True
233 except OSError, inst:
237 except OSError, inst:
234 return inst.errno != errno.ESRCH
238 return inst.errno != errno.ESRCH
235
239
236 def explainexit(code):
240 def explainexit(code):
237 """return a 2-tuple (desc, code) describing a subprocess status
241 """return a 2-tuple (desc, code) describing a subprocess status
238 (codes from kill are negative - not os.system/wait encoding)"""
242 (codes from kill are negative - not os.system/wait encoding)"""
239 if code >= 0:
243 if code >= 0:
240 return _("exited with status %d") % code, code
244 return _("exited with status %d") % code, code
241 return _("killed by signal %d") % -code, -code
245 return _("killed by signal %d") % -code, -code
242
246
243 def isowner(st):
247 def isowner(st):
244 """Return True if the stat object st is from the current user."""
248 """Return True if the stat object st is from the current user."""
245 return st.st_uid == os.getuid()
249 return st.st_uid == os.getuid()
246
250
247 def findexe(command):
251 def findexe(command):
248 '''Find executable for command searching like which does.
252 '''Find executable for command searching like which does.
249 If command is a basename then PATH is searched for command.
253 If command is a basename then PATH is searched for command.
250 PATH isn't searched if command is an absolute or relative path.
254 PATH isn't searched if command is an absolute or relative path.
251 If command isn't found None is returned.'''
255 If command isn't found None is returned.'''
252 if sys.platform == 'OpenVMS':
256 if sys.platform == 'OpenVMS':
253 return command
257 return command
254
258
255 def findexisting(executable):
259 def findexisting(executable):
256 'Will return executable if existing file'
260 'Will return executable if existing file'
257 if os.path.exists(executable):
261 if os.path.exists(executable):
258 return executable
262 return executable
259 return None
263 return None
260
264
261 if os.sep in command:
265 if os.sep in command:
262 return findexisting(command)
266 return findexisting(command)
263
267
264 for path in os.environ.get('PATH', '').split(os.pathsep):
268 for path in os.environ.get('PATH', '').split(os.pathsep):
265 executable = findexisting(os.path.join(path, command))
269 executable = findexisting(os.path.join(path, command))
266 if executable is not None:
270 if executable is not None:
267 st = os.stat(executable)
271 st = os.stat(executable)
268 if (st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)):
272 if (st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)):
269 return executable
273 return executable
270 return None
274 return None
271
275
272 def setsignalhandler():
276 def setsignalhandler():
273 pass
277 pass
274
278
275 def statfiles(files):
279 def statfiles(files):
276 'Stat each file in files and yield stat or None if file does not exist.'
280 'Stat each file in files and yield stat or None if file does not exist.'
277 lstat = os.lstat
281 lstat = os.lstat
278 for nf in files:
282 for nf in files:
279 try:
283 try:
280 st = lstat(nf)
284 st = lstat(nf)
281 except OSError, err:
285 except OSError, err:
282 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
286 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
283 raise
287 raise
284 st = None
288 st = None
285 yield st
289 yield st
286
290
287 def getuser():
291 def getuser():
288 '''return name of current user'''
292 '''return name of current user'''
289 return getpass.getuser()
293 return getpass.getuser()
290
294
291 def username(uid=None):
295 def username(uid=None):
292 """Return the name of the user with the given uid.
296 """Return the name of the user with the given uid.
293
297
294 If uid is None, return the name of the current user."""
298 If uid is None, return the name of the current user."""
295
299
296 if uid is None:
300 if uid is None:
297 uid = os.getuid()
301 uid = os.getuid()
298 try:
302 try:
299 return pwd.getpwuid(uid)[0]
303 return pwd.getpwuid(uid)[0]
300 except KeyError:
304 except KeyError:
301 return str(uid)
305 return str(uid)
302
306
303 def groupname(gid=None):
307 def groupname(gid=None):
304 """Return the name of the group with the given gid.
308 """Return the name of the group with the given gid.
305
309
306 If gid is None, return the name of the current group."""
310 If gid is None, return the name of the current group."""
307
311
308 if gid is None:
312 if gid is None:
309 gid = os.getgid()
313 gid = os.getgid()
310 try:
314 try:
311 return grp.getgrgid(gid)[0]
315 return grp.getgrgid(gid)[0]
312 except KeyError:
316 except KeyError:
313 return str(gid)
317 return str(gid)
314
318
315 def groupmembers(name):
319 def groupmembers(name):
316 """Return the list of members of the group with the given
320 """Return the list of members of the group with the given
317 name, KeyError if the group does not exist.
321 name, KeyError if the group does not exist.
318 """
322 """
319 return list(grp.getgrnam(name).gr_mem)
323 return list(grp.getgrnam(name).gr_mem)
320
324
321 def spawndetached(args):
325 def spawndetached(args):
322 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
326 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
323 args[0], args)
327 args[0], args)
324
328
325 def gethgcmd():
329 def gethgcmd():
326 return sys.argv[:1]
330 return sys.argv[:1]
327
331
328 def termwidth():
332 def termwidth():
329 try:
333 try:
330 import termios, array, fcntl
334 import termios, array, fcntl
331 for dev in (sys.stderr, sys.stdout, sys.stdin):
335 for dev in (sys.stderr, sys.stdout, sys.stdin):
332 try:
336 try:
333 try:
337 try:
334 fd = dev.fileno()
338 fd = dev.fileno()
335 except AttributeError:
339 except AttributeError:
336 continue
340 continue
337 if not os.isatty(fd):
341 if not os.isatty(fd):
338 continue
342 continue
339 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
343 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
340 width = array.array('h', arri)[1]
344 width = array.array('h', arri)[1]
341 if width > 0:
345 if width > 0:
342 return width
346 return width
343 except ValueError:
347 except ValueError:
344 pass
348 pass
345 except IOError, e:
349 except IOError, e:
346 if e[0] == errno.EINVAL:
350 if e[0] == errno.EINVAL:
347 pass
351 pass
348 else:
352 else:
349 raise
353 raise
350 except ImportError:
354 except ImportError:
351 pass
355 pass
352 return 80
356 return 80
353
357
354 def makedir(path, notindexed):
358 def makedir(path, notindexed):
355 os.mkdir(path)
359 os.mkdir(path)
356
360
357 def unlinkpath(f):
361 def unlinkpath(f):
358 """unlink and remove the directory if it is empty"""
362 """unlink and remove the directory if it is empty"""
359 os.unlink(f)
363 os.unlink(f)
360 # try removing directories that might now be empty
364 # try removing directories that might now be empty
361 try:
365 try:
362 os.removedirs(os.path.dirname(f))
366 os.removedirs(os.path.dirname(f))
363 except OSError:
367 except OSError:
364 pass
368 pass
365
369
366 def lookupreg(key, name=None, scope=None):
370 def lookupreg(key, name=None, scope=None):
367 return None
371 return None
368
372
369 def hidewindow():
373 def hidewindow():
370 """Hide current shell window.
374 """Hide current shell window.
371
375
372 Used to hide the window opened when starting asynchronous
376 Used to hide the window opened when starting asynchronous
373 child process under Windows, unneeded on other systems.
377 child process under Windows, unneeded on other systems.
374 """
378 """
375 pass
379 pass
376
380
377 class cachestat(object):
381 class cachestat(object):
378 def __init__(self, path):
382 def __init__(self, path):
379 self.stat = os.stat(path)
383 self.stat = os.stat(path)
380
384
381 def cacheable(self):
385 def cacheable(self):
382 return bool(self.stat.st_ino)
386 return bool(self.stat.st_ino)
383
387
384 def __eq__(self, other):
388 def __eq__(self, other):
385 try:
389 try:
386 return self.stat == other.stat
390 return self.stat == other.stat
387 except AttributeError:
391 except AttributeError:
388 return False
392 return False
389
393
390 def __ne__(self, other):
394 def __ne__(self, other):
391 return not self == other
395 return not self == other
392
396
393 def executablepath():
397 def executablepath():
394 return None # available on Windows only
398 return None # available on Windows only
@@ -1,1741 +1,1742 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, signal
19 import os, time, calendar, textwrap, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 if os.name == 'nt':
22 if os.name == 'nt':
23 import windows as platform
23 import windows as platform
24 else:
24 else:
25 import posix as platform
25 import posix as platform
26
26
27 cachestat = platform.cachestat
27 cachestat = platform.cachestat
28 checkexec = platform.checkexec
28 checkexec = platform.checkexec
29 checklink = platform.checklink
29 checklink = platform.checklink
30 copymode = platform.copymode
30 copymode = platform.copymode
31 executablepath = platform.executablepath
31 executablepath = platform.executablepath
32 expandglobs = platform.expandglobs
32 expandglobs = platform.expandglobs
33 explainexit = platform.explainexit
33 explainexit = platform.explainexit
34 findexe = platform.findexe
34 findexe = platform.findexe
35 gethgcmd = platform.gethgcmd
35 gethgcmd = platform.gethgcmd
36 getuser = platform.getuser
36 getuser = platform.getuser
37 groupmembers = platform.groupmembers
37 groupmembers = platform.groupmembers
38 groupname = platform.groupname
38 groupname = platform.groupname
39 hidewindow = platform.hidewindow
39 hidewindow = platform.hidewindow
40 isexec = platform.isexec
40 isexec = platform.isexec
41 isowner = platform.isowner
41 isowner = platform.isowner
42 localpath = platform.localpath
42 localpath = platform.localpath
43 lookupreg = platform.lookupreg
43 lookupreg = platform.lookupreg
44 makedir = platform.makedir
44 makedir = platform.makedir
45 nlinks = platform.nlinks
45 nlinks = platform.nlinks
46 normpath = platform.normpath
46 normpath = platform.normpath
47 normcase = platform.normcase
47 nulldev = platform.nulldev
48 nulldev = platform.nulldev
48 openhardlinks = platform.openhardlinks
49 openhardlinks = platform.openhardlinks
49 oslink = platform.oslink
50 oslink = platform.oslink
50 parsepatchoutput = platform.parsepatchoutput
51 parsepatchoutput = platform.parsepatchoutput
51 pconvert = platform.pconvert
52 pconvert = platform.pconvert
52 popen = platform.popen
53 popen = platform.popen
53 posixfile = platform.posixfile
54 posixfile = platform.posixfile
54 quotecommand = platform.quotecommand
55 quotecommand = platform.quotecommand
55 realpath = platform.realpath
56 realpath = platform.realpath
56 rename = platform.rename
57 rename = platform.rename
57 samedevice = platform.samedevice
58 samedevice = platform.samedevice
58 samefile = platform.samefile
59 samefile = platform.samefile
59 samestat = platform.samestat
60 samestat = platform.samestat
60 setbinary = platform.setbinary
61 setbinary = platform.setbinary
61 setflags = platform.setflags
62 setflags = platform.setflags
62 setsignalhandler = platform.setsignalhandler
63 setsignalhandler = platform.setsignalhandler
63 shellquote = platform.shellquote
64 shellquote = platform.shellquote
64 spawndetached = platform.spawndetached
65 spawndetached = platform.spawndetached
65 sshargs = platform.sshargs
66 sshargs = platform.sshargs
66 statfiles = platform.statfiles
67 statfiles = platform.statfiles
67 termwidth = platform.termwidth
68 termwidth = platform.termwidth
68 testpid = platform.testpid
69 testpid = platform.testpid
69 umask = platform.umask
70 umask = platform.umask
70 unlink = platform.unlink
71 unlink = platform.unlink
71 unlinkpath = platform.unlinkpath
72 unlinkpath = platform.unlinkpath
72 username = platform.username
73 username = platform.username
73
74
74 # Python compatibility
75 # Python compatibility
75
76
76 def sha1(s=''):
77 def sha1(s=''):
77 '''
78 '''
78 Low-overhead wrapper around Python's SHA support
79 Low-overhead wrapper around Python's SHA support
79
80
80 >>> f = _fastsha1
81 >>> f = _fastsha1
81 >>> a = sha1()
82 >>> a = sha1()
82 >>> a = f()
83 >>> a = f()
83 >>> a.hexdigest()
84 >>> a.hexdigest()
84 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
85 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
85 '''
86 '''
86
87
87 return _fastsha1(s)
88 return _fastsha1(s)
88
89
89 _notset = object()
90 _notset = object()
90 def safehasattr(thing, attr):
91 def safehasattr(thing, attr):
91 return getattr(thing, attr, _notset) is not _notset
92 return getattr(thing, attr, _notset) is not _notset
92
93
93 def _fastsha1(s=''):
94 def _fastsha1(s=''):
94 # This function will import sha1 from hashlib or sha (whichever is
95 # This function will import sha1 from hashlib or sha (whichever is
95 # available) and overwrite itself with it on the first call.
96 # available) and overwrite itself with it on the first call.
96 # Subsequent calls will go directly to the imported function.
97 # Subsequent calls will go directly to the imported function.
97 if sys.version_info >= (2, 5):
98 if sys.version_info >= (2, 5):
98 from hashlib import sha1 as _sha1
99 from hashlib import sha1 as _sha1
99 else:
100 else:
100 from sha import sha as _sha1
101 from sha import sha as _sha1
101 global _fastsha1, sha1
102 global _fastsha1, sha1
102 _fastsha1 = sha1 = _sha1
103 _fastsha1 = sha1 = _sha1
103 return _sha1(s)
104 return _sha1(s)
104
105
105 import __builtin__
106 import __builtin__
106
107
107 if sys.version_info[0] < 3:
108 if sys.version_info[0] < 3:
108 def fakebuffer(sliceable, offset=0):
109 def fakebuffer(sliceable, offset=0):
109 return sliceable[offset:]
110 return sliceable[offset:]
110 else:
111 else:
111 def fakebuffer(sliceable, offset=0):
112 def fakebuffer(sliceable, offset=0):
112 return memoryview(sliceable)[offset:]
113 return memoryview(sliceable)[offset:]
113 try:
114 try:
114 buffer
115 buffer
115 except NameError:
116 except NameError:
116 __builtin__.buffer = fakebuffer
117 __builtin__.buffer = fakebuffer
117
118
118 import subprocess
119 import subprocess
119 closefds = os.name == 'posix'
120 closefds = os.name == 'posix'
120
121
121 def popen2(cmd, env=None, newlines=False):
122 def popen2(cmd, env=None, newlines=False):
122 # Setting bufsize to -1 lets the system decide the buffer size.
123 # Setting bufsize to -1 lets the system decide the buffer size.
123 # The default for bufsize is 0, meaning unbuffered. This leads to
124 # The default for bufsize is 0, meaning unbuffered. This leads to
124 # poor performance on Mac OS X: http://bugs.python.org/issue4194
125 # poor performance on Mac OS X: http://bugs.python.org/issue4194
125 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
126 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
126 close_fds=closefds,
127 close_fds=closefds,
127 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
128 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
128 universal_newlines=newlines,
129 universal_newlines=newlines,
129 env=env)
130 env=env)
130 return p.stdin, p.stdout
131 return p.stdin, p.stdout
131
132
132 def popen3(cmd, env=None, newlines=False):
133 def popen3(cmd, env=None, newlines=False):
133 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
134 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
134 close_fds=closefds,
135 close_fds=closefds,
135 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
136 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
136 stderr=subprocess.PIPE,
137 stderr=subprocess.PIPE,
137 universal_newlines=newlines,
138 universal_newlines=newlines,
138 env=env)
139 env=env)
139 return p.stdin, p.stdout, p.stderr
140 return p.stdin, p.stdout, p.stderr
140
141
141 def version():
142 def version():
142 """Return version information if available."""
143 """Return version information if available."""
143 try:
144 try:
144 import __version__
145 import __version__
145 return __version__.version
146 return __version__.version
146 except ImportError:
147 except ImportError:
147 return 'unknown'
148 return 'unknown'
148
149
149 # used by parsedate
150 # used by parsedate
150 defaultdateformats = (
151 defaultdateformats = (
151 '%Y-%m-%d %H:%M:%S',
152 '%Y-%m-%d %H:%M:%S',
152 '%Y-%m-%d %I:%M:%S%p',
153 '%Y-%m-%d %I:%M:%S%p',
153 '%Y-%m-%d %H:%M',
154 '%Y-%m-%d %H:%M',
154 '%Y-%m-%d %I:%M%p',
155 '%Y-%m-%d %I:%M%p',
155 '%Y-%m-%d',
156 '%Y-%m-%d',
156 '%m-%d',
157 '%m-%d',
157 '%m/%d',
158 '%m/%d',
158 '%m/%d/%y',
159 '%m/%d/%y',
159 '%m/%d/%Y',
160 '%m/%d/%Y',
160 '%a %b %d %H:%M:%S %Y',
161 '%a %b %d %H:%M:%S %Y',
161 '%a %b %d %I:%M:%S%p %Y',
162 '%a %b %d %I:%M:%S%p %Y',
162 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
163 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
163 '%b %d %H:%M:%S %Y',
164 '%b %d %H:%M:%S %Y',
164 '%b %d %I:%M:%S%p %Y',
165 '%b %d %I:%M:%S%p %Y',
165 '%b %d %H:%M:%S',
166 '%b %d %H:%M:%S',
166 '%b %d %I:%M:%S%p',
167 '%b %d %I:%M:%S%p',
167 '%b %d %H:%M',
168 '%b %d %H:%M',
168 '%b %d %I:%M%p',
169 '%b %d %I:%M%p',
169 '%b %d %Y',
170 '%b %d %Y',
170 '%b %d',
171 '%b %d',
171 '%H:%M:%S',
172 '%H:%M:%S',
172 '%I:%M:%S%p',
173 '%I:%M:%S%p',
173 '%H:%M',
174 '%H:%M',
174 '%I:%M%p',
175 '%I:%M%p',
175 )
176 )
176
177
177 extendeddateformats = defaultdateformats + (
178 extendeddateformats = defaultdateformats + (
178 "%Y",
179 "%Y",
179 "%Y-%m",
180 "%Y-%m",
180 "%b",
181 "%b",
181 "%b %Y",
182 "%b %Y",
182 )
183 )
183
184
184 def cachefunc(func):
185 def cachefunc(func):
185 '''cache the result of function calls'''
186 '''cache the result of function calls'''
186 # XXX doesn't handle keywords args
187 # XXX doesn't handle keywords args
187 cache = {}
188 cache = {}
188 if func.func_code.co_argcount == 1:
189 if func.func_code.co_argcount == 1:
189 # we gain a small amount of time because
190 # we gain a small amount of time because
190 # we don't need to pack/unpack the list
191 # we don't need to pack/unpack the list
191 def f(arg):
192 def f(arg):
192 if arg not in cache:
193 if arg not in cache:
193 cache[arg] = func(arg)
194 cache[arg] = func(arg)
194 return cache[arg]
195 return cache[arg]
195 else:
196 else:
196 def f(*args):
197 def f(*args):
197 if args not in cache:
198 if args not in cache:
198 cache[args] = func(*args)
199 cache[args] = func(*args)
199 return cache[args]
200 return cache[args]
200
201
201 return f
202 return f
202
203
203 def lrucachefunc(func):
204 def lrucachefunc(func):
204 '''cache most recent results of function calls'''
205 '''cache most recent results of function calls'''
205 cache = {}
206 cache = {}
206 order = []
207 order = []
207 if func.func_code.co_argcount == 1:
208 if func.func_code.co_argcount == 1:
208 def f(arg):
209 def f(arg):
209 if arg not in cache:
210 if arg not in cache:
210 if len(cache) > 20:
211 if len(cache) > 20:
211 del cache[order.pop(0)]
212 del cache[order.pop(0)]
212 cache[arg] = func(arg)
213 cache[arg] = func(arg)
213 else:
214 else:
214 order.remove(arg)
215 order.remove(arg)
215 order.append(arg)
216 order.append(arg)
216 return cache[arg]
217 return cache[arg]
217 else:
218 else:
218 def f(*args):
219 def f(*args):
219 if args not in cache:
220 if args not in cache:
220 if len(cache) > 20:
221 if len(cache) > 20:
221 del cache[order.pop(0)]
222 del cache[order.pop(0)]
222 cache[args] = func(*args)
223 cache[args] = func(*args)
223 else:
224 else:
224 order.remove(args)
225 order.remove(args)
225 order.append(args)
226 order.append(args)
226 return cache[args]
227 return cache[args]
227
228
228 return f
229 return f
229
230
230 class propertycache(object):
231 class propertycache(object):
231 def __init__(self, func):
232 def __init__(self, func):
232 self.func = func
233 self.func = func
233 self.name = func.__name__
234 self.name = func.__name__
234 def __get__(self, obj, type=None):
235 def __get__(self, obj, type=None):
235 result = self.func(obj)
236 result = self.func(obj)
236 setattr(obj, self.name, result)
237 setattr(obj, self.name, result)
237 return result
238 return result
238
239
239 def pipefilter(s, cmd):
240 def pipefilter(s, cmd):
240 '''filter string S through command CMD, returning its output'''
241 '''filter string S through command CMD, returning its output'''
241 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
242 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
242 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
243 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
243 pout, perr = p.communicate(s)
244 pout, perr = p.communicate(s)
244 return pout
245 return pout
245
246
246 def tempfilter(s, cmd):
247 def tempfilter(s, cmd):
247 '''filter string S through a pair of temporary files with CMD.
248 '''filter string S through a pair of temporary files with CMD.
248 CMD is used as a template to create the real command to be run,
249 CMD is used as a template to create the real command to be run,
249 with the strings INFILE and OUTFILE replaced by the real names of
250 with the strings INFILE and OUTFILE replaced by the real names of
250 the temporary files generated.'''
251 the temporary files generated.'''
251 inname, outname = None, None
252 inname, outname = None, None
252 try:
253 try:
253 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
254 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
254 fp = os.fdopen(infd, 'wb')
255 fp = os.fdopen(infd, 'wb')
255 fp.write(s)
256 fp.write(s)
256 fp.close()
257 fp.close()
257 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
258 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
258 os.close(outfd)
259 os.close(outfd)
259 cmd = cmd.replace('INFILE', inname)
260 cmd = cmd.replace('INFILE', inname)
260 cmd = cmd.replace('OUTFILE', outname)
261 cmd = cmd.replace('OUTFILE', outname)
261 code = os.system(cmd)
262 code = os.system(cmd)
262 if sys.platform == 'OpenVMS' and code & 1:
263 if sys.platform == 'OpenVMS' and code & 1:
263 code = 0
264 code = 0
264 if code:
265 if code:
265 raise Abort(_("command '%s' failed: %s") %
266 raise Abort(_("command '%s' failed: %s") %
266 (cmd, explainexit(code)))
267 (cmd, explainexit(code)))
267 fp = open(outname, 'rb')
268 fp = open(outname, 'rb')
268 r = fp.read()
269 r = fp.read()
269 fp.close()
270 fp.close()
270 return r
271 return r
271 finally:
272 finally:
272 try:
273 try:
273 if inname:
274 if inname:
274 os.unlink(inname)
275 os.unlink(inname)
275 except OSError:
276 except OSError:
276 pass
277 pass
277 try:
278 try:
278 if outname:
279 if outname:
279 os.unlink(outname)
280 os.unlink(outname)
280 except OSError:
281 except OSError:
281 pass
282 pass
282
283
283 filtertable = {
284 filtertable = {
284 'tempfile:': tempfilter,
285 'tempfile:': tempfilter,
285 'pipe:': pipefilter,
286 'pipe:': pipefilter,
286 }
287 }
287
288
288 def filter(s, cmd):
289 def filter(s, cmd):
289 "filter a string through a command that transforms its input to its output"
290 "filter a string through a command that transforms its input to its output"
290 for name, fn in filtertable.iteritems():
291 for name, fn in filtertable.iteritems():
291 if cmd.startswith(name):
292 if cmd.startswith(name):
292 return fn(s, cmd[len(name):].lstrip())
293 return fn(s, cmd[len(name):].lstrip())
293 return pipefilter(s, cmd)
294 return pipefilter(s, cmd)
294
295
295 def binary(s):
296 def binary(s):
296 """return true if a string is binary data"""
297 """return true if a string is binary data"""
297 return bool(s and '\0' in s)
298 return bool(s and '\0' in s)
298
299
299 def increasingchunks(source, min=1024, max=65536):
300 def increasingchunks(source, min=1024, max=65536):
300 '''return no less than min bytes per chunk while data remains,
301 '''return no less than min bytes per chunk while data remains,
301 doubling min after each chunk until it reaches max'''
302 doubling min after each chunk until it reaches max'''
302 def log2(x):
303 def log2(x):
303 if not x:
304 if not x:
304 return 0
305 return 0
305 i = 0
306 i = 0
306 while x:
307 while x:
307 x >>= 1
308 x >>= 1
308 i += 1
309 i += 1
309 return i - 1
310 return i - 1
310
311
311 buf = []
312 buf = []
312 blen = 0
313 blen = 0
313 for chunk in source:
314 for chunk in source:
314 buf.append(chunk)
315 buf.append(chunk)
315 blen += len(chunk)
316 blen += len(chunk)
316 if blen >= min:
317 if blen >= min:
317 if min < max:
318 if min < max:
318 min = min << 1
319 min = min << 1
319 nmin = 1 << log2(blen)
320 nmin = 1 << log2(blen)
320 if nmin > min:
321 if nmin > min:
321 min = nmin
322 min = nmin
322 if min > max:
323 if min > max:
323 min = max
324 min = max
324 yield ''.join(buf)
325 yield ''.join(buf)
325 blen = 0
326 blen = 0
326 buf = []
327 buf = []
327 if buf:
328 if buf:
328 yield ''.join(buf)
329 yield ''.join(buf)
329
330
330 Abort = error.Abort
331 Abort = error.Abort
331
332
332 def always(fn):
333 def always(fn):
333 return True
334 return True
334
335
335 def never(fn):
336 def never(fn):
336 return False
337 return False
337
338
338 def pathto(root, n1, n2):
339 def pathto(root, n1, n2):
339 '''return the relative path from one place to another.
340 '''return the relative path from one place to another.
340 root should use os.sep to separate directories
341 root should use os.sep to separate directories
341 n1 should use os.sep to separate directories
342 n1 should use os.sep to separate directories
342 n2 should use "/" to separate directories
343 n2 should use "/" to separate directories
343 returns an os.sep-separated path.
344 returns an os.sep-separated path.
344
345
345 If n1 is a relative path, it's assumed it's
346 If n1 is a relative path, it's assumed it's
346 relative to root.
347 relative to root.
347 n2 should always be relative to root.
348 n2 should always be relative to root.
348 '''
349 '''
349 if not n1:
350 if not n1:
350 return localpath(n2)
351 return localpath(n2)
351 if os.path.isabs(n1):
352 if os.path.isabs(n1):
352 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
353 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
353 return os.path.join(root, localpath(n2))
354 return os.path.join(root, localpath(n2))
354 n2 = '/'.join((pconvert(root), n2))
355 n2 = '/'.join((pconvert(root), n2))
355 a, b = splitpath(n1), n2.split('/')
356 a, b = splitpath(n1), n2.split('/')
356 a.reverse()
357 a.reverse()
357 b.reverse()
358 b.reverse()
358 while a and b and a[-1] == b[-1]:
359 while a and b and a[-1] == b[-1]:
359 a.pop()
360 a.pop()
360 b.pop()
361 b.pop()
361 b.reverse()
362 b.reverse()
362 return os.sep.join((['..'] * len(a)) + b) or '.'
363 return os.sep.join((['..'] * len(a)) + b) or '.'
363
364
364 _hgexecutable = None
365 _hgexecutable = None
365
366
366 def mainfrozen():
367 def mainfrozen():
367 """return True if we are a frozen executable.
368 """return True if we are a frozen executable.
368
369
369 The code supports py2exe (most common, Windows only) and tools/freeze
370 The code supports py2exe (most common, Windows only) and tools/freeze
370 (portable, not much used).
371 (portable, not much used).
371 """
372 """
372 return (safehasattr(sys, "frozen") or # new py2exe
373 return (safehasattr(sys, "frozen") or # new py2exe
373 safehasattr(sys, "importers") or # old py2exe
374 safehasattr(sys, "importers") or # old py2exe
374 imp.is_frozen("__main__")) # tools/freeze
375 imp.is_frozen("__main__")) # tools/freeze
375
376
376 def hgexecutable():
377 def hgexecutable():
377 """return location of the 'hg' executable.
378 """return location of the 'hg' executable.
378
379
379 Defaults to $HG or 'hg' in the search path.
380 Defaults to $HG or 'hg' in the search path.
380 """
381 """
381 if _hgexecutable is None:
382 if _hgexecutable is None:
382 hg = os.environ.get('HG')
383 hg = os.environ.get('HG')
383 mainmod = sys.modules['__main__']
384 mainmod = sys.modules['__main__']
384 if hg:
385 if hg:
385 _sethgexecutable(hg)
386 _sethgexecutable(hg)
386 elif mainfrozen():
387 elif mainfrozen():
387 _sethgexecutable(sys.executable)
388 _sethgexecutable(sys.executable)
388 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
389 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
389 _sethgexecutable(mainmod.__file__)
390 _sethgexecutable(mainmod.__file__)
390 else:
391 else:
391 exe = findexe('hg') or os.path.basename(sys.argv[0])
392 exe = findexe('hg') or os.path.basename(sys.argv[0])
392 _sethgexecutable(exe)
393 _sethgexecutable(exe)
393 return _hgexecutable
394 return _hgexecutable
394
395
395 def _sethgexecutable(path):
396 def _sethgexecutable(path):
396 """set location of the 'hg' executable"""
397 """set location of the 'hg' executable"""
397 global _hgexecutable
398 global _hgexecutable
398 _hgexecutable = path
399 _hgexecutable = path
399
400
400 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
401 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
401 '''enhanced shell command execution.
402 '''enhanced shell command execution.
402 run with environment maybe modified, maybe in different dir.
403 run with environment maybe modified, maybe in different dir.
403
404
404 if command fails and onerr is None, return status. if ui object,
405 if command fails and onerr is None, return status. if ui object,
405 print error message and return status, else raise onerr object as
406 print error message and return status, else raise onerr object as
406 exception.
407 exception.
407
408
408 if out is specified, it is assumed to be a file-like object that has a
409 if out is specified, it is assumed to be a file-like object that has a
409 write() method. stdout and stderr will be redirected to out.'''
410 write() method. stdout and stderr will be redirected to out.'''
410 try:
411 try:
411 sys.stdout.flush()
412 sys.stdout.flush()
412 except Exception:
413 except Exception:
413 pass
414 pass
414 def py2shell(val):
415 def py2shell(val):
415 'convert python object into string that is useful to shell'
416 'convert python object into string that is useful to shell'
416 if val is None or val is False:
417 if val is None or val is False:
417 return '0'
418 return '0'
418 if val is True:
419 if val is True:
419 return '1'
420 return '1'
420 return str(val)
421 return str(val)
421 origcmd = cmd
422 origcmd = cmd
422 cmd = quotecommand(cmd)
423 cmd = quotecommand(cmd)
423 env = dict(os.environ)
424 env = dict(os.environ)
424 env.update((k, py2shell(v)) for k, v in environ.iteritems())
425 env.update((k, py2shell(v)) for k, v in environ.iteritems())
425 env['HG'] = hgexecutable()
426 env['HG'] = hgexecutable()
426 if out is None or out == sys.__stdout__:
427 if out is None or out == sys.__stdout__:
427 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
428 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
428 env=env, cwd=cwd)
429 env=env, cwd=cwd)
429 else:
430 else:
430 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
431 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
431 env=env, cwd=cwd, stdout=subprocess.PIPE,
432 env=env, cwd=cwd, stdout=subprocess.PIPE,
432 stderr=subprocess.STDOUT)
433 stderr=subprocess.STDOUT)
433 for line in proc.stdout:
434 for line in proc.stdout:
434 out.write(line)
435 out.write(line)
435 proc.wait()
436 proc.wait()
436 rc = proc.returncode
437 rc = proc.returncode
437 if sys.platform == 'OpenVMS' and rc & 1:
438 if sys.platform == 'OpenVMS' and rc & 1:
438 rc = 0
439 rc = 0
439 if rc and onerr:
440 if rc and onerr:
440 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
441 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
441 explainexit(rc)[0])
442 explainexit(rc)[0])
442 if errprefix:
443 if errprefix:
443 errmsg = '%s: %s' % (errprefix, errmsg)
444 errmsg = '%s: %s' % (errprefix, errmsg)
444 try:
445 try:
445 onerr.warn(errmsg + '\n')
446 onerr.warn(errmsg + '\n')
446 except AttributeError:
447 except AttributeError:
447 raise onerr(errmsg)
448 raise onerr(errmsg)
448 return rc
449 return rc
449
450
450 def checksignature(func):
451 def checksignature(func):
451 '''wrap a function with code to check for calling errors'''
452 '''wrap a function with code to check for calling errors'''
452 def check(*args, **kwargs):
453 def check(*args, **kwargs):
453 try:
454 try:
454 return func(*args, **kwargs)
455 return func(*args, **kwargs)
455 except TypeError:
456 except TypeError:
456 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
457 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
457 raise error.SignatureError
458 raise error.SignatureError
458 raise
459 raise
459
460
460 return check
461 return check
461
462
462 def copyfile(src, dest):
463 def copyfile(src, dest):
463 "copy a file, preserving mode and atime/mtime"
464 "copy a file, preserving mode and atime/mtime"
464 if os.path.islink(src):
465 if os.path.islink(src):
465 try:
466 try:
466 os.unlink(dest)
467 os.unlink(dest)
467 except OSError:
468 except OSError:
468 pass
469 pass
469 os.symlink(os.readlink(src), dest)
470 os.symlink(os.readlink(src), dest)
470 else:
471 else:
471 try:
472 try:
472 shutil.copyfile(src, dest)
473 shutil.copyfile(src, dest)
473 shutil.copymode(src, dest)
474 shutil.copymode(src, dest)
474 except shutil.Error, inst:
475 except shutil.Error, inst:
475 raise Abort(str(inst))
476 raise Abort(str(inst))
476
477
477 def copyfiles(src, dst, hardlink=None):
478 def copyfiles(src, dst, hardlink=None):
478 """Copy a directory tree using hardlinks if possible"""
479 """Copy a directory tree using hardlinks if possible"""
479
480
480 if hardlink is None:
481 if hardlink is None:
481 hardlink = (os.stat(src).st_dev ==
482 hardlink = (os.stat(src).st_dev ==
482 os.stat(os.path.dirname(dst)).st_dev)
483 os.stat(os.path.dirname(dst)).st_dev)
483
484
484 num = 0
485 num = 0
485 if os.path.isdir(src):
486 if os.path.isdir(src):
486 os.mkdir(dst)
487 os.mkdir(dst)
487 for name, kind in osutil.listdir(src):
488 for name, kind in osutil.listdir(src):
488 srcname = os.path.join(src, name)
489 srcname = os.path.join(src, name)
489 dstname = os.path.join(dst, name)
490 dstname = os.path.join(dst, name)
490 hardlink, n = copyfiles(srcname, dstname, hardlink)
491 hardlink, n = copyfiles(srcname, dstname, hardlink)
491 num += n
492 num += n
492 else:
493 else:
493 if hardlink:
494 if hardlink:
494 try:
495 try:
495 oslink(src, dst)
496 oslink(src, dst)
496 except (IOError, OSError):
497 except (IOError, OSError):
497 hardlink = False
498 hardlink = False
498 shutil.copy(src, dst)
499 shutil.copy(src, dst)
499 else:
500 else:
500 shutil.copy(src, dst)
501 shutil.copy(src, dst)
501 num += 1
502 num += 1
502
503
503 return hardlink, num
504 return hardlink, num
504
505
505 _winreservednames = '''con prn aux nul
506 _winreservednames = '''con prn aux nul
506 com1 com2 com3 com4 com5 com6 com7 com8 com9
507 com1 com2 com3 com4 com5 com6 com7 com8 com9
507 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
508 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
508 _winreservedchars = ':*?"<>|'
509 _winreservedchars = ':*?"<>|'
509 def checkwinfilename(path):
510 def checkwinfilename(path):
510 '''Check that the base-relative path is a valid filename on Windows.
511 '''Check that the base-relative path is a valid filename on Windows.
511 Returns None if the path is ok, or a UI string describing the problem.
512 Returns None if the path is ok, or a UI string describing the problem.
512
513
513 >>> checkwinfilename("just/a/normal/path")
514 >>> checkwinfilename("just/a/normal/path")
514 >>> checkwinfilename("foo/bar/con.xml")
515 >>> checkwinfilename("foo/bar/con.xml")
515 "filename contains 'con', which is reserved on Windows"
516 "filename contains 'con', which is reserved on Windows"
516 >>> checkwinfilename("foo/con.xml/bar")
517 >>> checkwinfilename("foo/con.xml/bar")
517 "filename contains 'con', which is reserved on Windows"
518 "filename contains 'con', which is reserved on Windows"
518 >>> checkwinfilename("foo/bar/xml.con")
519 >>> checkwinfilename("foo/bar/xml.con")
519 >>> checkwinfilename("foo/bar/AUX/bla.txt")
520 >>> checkwinfilename("foo/bar/AUX/bla.txt")
520 "filename contains 'AUX', which is reserved on Windows"
521 "filename contains 'AUX', which is reserved on Windows"
521 >>> checkwinfilename("foo/bar/bla:.txt")
522 >>> checkwinfilename("foo/bar/bla:.txt")
522 "filename contains ':', which is reserved on Windows"
523 "filename contains ':', which is reserved on Windows"
523 >>> checkwinfilename("foo/bar/b\07la.txt")
524 >>> checkwinfilename("foo/bar/b\07la.txt")
524 "filename contains '\\\\x07', which is invalid on Windows"
525 "filename contains '\\\\x07', which is invalid on Windows"
525 >>> checkwinfilename("foo/bar/bla ")
526 >>> checkwinfilename("foo/bar/bla ")
526 "filename ends with ' ', which is not allowed on Windows"
527 "filename ends with ' ', which is not allowed on Windows"
527 >>> checkwinfilename("../bar")
528 >>> checkwinfilename("../bar")
528 '''
529 '''
529 for n in path.replace('\\', '/').split('/'):
530 for n in path.replace('\\', '/').split('/'):
530 if not n:
531 if not n:
531 continue
532 continue
532 for c in n:
533 for c in n:
533 if c in _winreservedchars:
534 if c in _winreservedchars:
534 return _("filename contains '%s', which is reserved "
535 return _("filename contains '%s', which is reserved "
535 "on Windows") % c
536 "on Windows") % c
536 if ord(c) <= 31:
537 if ord(c) <= 31:
537 return _("filename contains %r, which is invalid "
538 return _("filename contains %r, which is invalid "
538 "on Windows") % c
539 "on Windows") % c
539 base = n.split('.')[0]
540 base = n.split('.')[0]
540 if base and base.lower() in _winreservednames:
541 if base and base.lower() in _winreservednames:
541 return _("filename contains '%s', which is reserved "
542 return _("filename contains '%s', which is reserved "
542 "on Windows") % base
543 "on Windows") % base
543 t = n[-1]
544 t = n[-1]
544 if t in '. ' and n not in '..':
545 if t in '. ' and n not in '..':
545 return _("filename ends with '%s', which is not allowed "
546 return _("filename ends with '%s', which is not allowed "
546 "on Windows") % t
547 "on Windows") % t
547
548
548 if os.name == 'nt':
549 if os.name == 'nt':
549 checkosfilename = checkwinfilename
550 checkosfilename = checkwinfilename
550 else:
551 else:
551 checkosfilename = platform.checkosfilename
552 checkosfilename = platform.checkosfilename
552
553
553 def makelock(info, pathname):
554 def makelock(info, pathname):
554 try:
555 try:
555 return os.symlink(info, pathname)
556 return os.symlink(info, pathname)
556 except OSError, why:
557 except OSError, why:
557 if why.errno == errno.EEXIST:
558 if why.errno == errno.EEXIST:
558 raise
559 raise
559 except AttributeError: # no symlink in os
560 except AttributeError: # no symlink in os
560 pass
561 pass
561
562
562 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
563 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
563 os.write(ld, info)
564 os.write(ld, info)
564 os.close(ld)
565 os.close(ld)
565
566
566 def readlock(pathname):
567 def readlock(pathname):
567 try:
568 try:
568 return os.readlink(pathname)
569 return os.readlink(pathname)
569 except OSError, why:
570 except OSError, why:
570 if why.errno not in (errno.EINVAL, errno.ENOSYS):
571 if why.errno not in (errno.EINVAL, errno.ENOSYS):
571 raise
572 raise
572 except AttributeError: # no symlink in os
573 except AttributeError: # no symlink in os
573 pass
574 pass
574 fp = posixfile(pathname)
575 fp = posixfile(pathname)
575 r = fp.read()
576 r = fp.read()
576 fp.close()
577 fp.close()
577 return r
578 return r
578
579
579 def fstat(fp):
580 def fstat(fp):
580 '''stat file object that may not have fileno method.'''
581 '''stat file object that may not have fileno method.'''
581 try:
582 try:
582 return os.fstat(fp.fileno())
583 return os.fstat(fp.fileno())
583 except AttributeError:
584 except AttributeError:
584 return os.stat(fp.name)
585 return os.stat(fp.name)
585
586
586 # File system features
587 # File system features
587
588
588 def checkcase(path):
589 def checkcase(path):
589 """
590 """
590 Check whether the given path is on a case-sensitive filesystem
591 Check whether the given path is on a case-sensitive filesystem
591
592
592 Requires a path (like /foo/.hg) ending with a foldable final
593 Requires a path (like /foo/.hg) ending with a foldable final
593 directory component.
594 directory component.
594 """
595 """
595 s1 = os.stat(path)
596 s1 = os.stat(path)
596 d, b = os.path.split(path)
597 d, b = os.path.split(path)
597 p2 = os.path.join(d, b.upper())
598 p2 = os.path.join(d, b.upper())
598 if path == p2:
599 if path == p2:
599 p2 = os.path.join(d, b.lower())
600 p2 = os.path.join(d, b.lower())
600 try:
601 try:
601 s2 = os.stat(p2)
602 s2 = os.stat(p2)
602 if s2 == s1:
603 if s2 == s1:
603 return False
604 return False
604 return True
605 return True
605 except OSError:
606 except OSError:
606 return True
607 return True
607
608
608 _fspathcache = {}
609 _fspathcache = {}
609 def fspath(name, root):
610 def fspath(name, root):
610 '''Get name in the case stored in the filesystem
611 '''Get name in the case stored in the filesystem
611
612
612 The name is either relative to root, or it is an absolute path starting
613 The name is either relative to root, or it is an absolute path starting
613 with root. Note that this function is unnecessary, and should not be
614 with root. Note that this function is unnecessary, and should not be
614 called, for case-sensitive filesystems (simply because it's expensive).
615 called, for case-sensitive filesystems (simply because it's expensive).
615 '''
616 '''
616 # If name is absolute, make it relative
617 # If name is absolute, make it relative
617 if name.lower().startswith(root.lower()):
618 if name.lower().startswith(root.lower()):
618 l = len(root)
619 l = len(root)
619 if name[l] == os.sep or name[l] == os.altsep:
620 if name[l] == os.sep or name[l] == os.altsep:
620 l = l + 1
621 l = l + 1
621 name = name[l:]
622 name = name[l:]
622
623
623 if not os.path.lexists(os.path.join(root, name)):
624 if not os.path.lexists(os.path.join(root, name)):
624 return None
625 return None
625
626
626 seps = os.sep
627 seps = os.sep
627 if os.altsep:
628 if os.altsep:
628 seps = seps + os.altsep
629 seps = seps + os.altsep
629 # Protect backslashes. This gets silly very quickly.
630 # Protect backslashes. This gets silly very quickly.
630 seps.replace('\\','\\\\')
631 seps.replace('\\','\\\\')
631 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
632 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
632 dir = os.path.normcase(os.path.normpath(root))
633 dir = os.path.normcase(os.path.normpath(root))
633 result = []
634 result = []
634 for part, sep in pattern.findall(name):
635 for part, sep in pattern.findall(name):
635 if sep:
636 if sep:
636 result.append(sep)
637 result.append(sep)
637 continue
638 continue
638
639
639 if dir not in _fspathcache:
640 if dir not in _fspathcache:
640 _fspathcache[dir] = os.listdir(dir)
641 _fspathcache[dir] = os.listdir(dir)
641 contents = _fspathcache[dir]
642 contents = _fspathcache[dir]
642
643
643 lpart = part.lower()
644 lpart = part.lower()
644 lenp = len(part)
645 lenp = len(part)
645 for n in contents:
646 for n in contents:
646 if lenp == len(n) and n.lower() == lpart:
647 if lenp == len(n) and n.lower() == lpart:
647 result.append(n)
648 result.append(n)
648 break
649 break
649 else:
650 else:
650 # Cannot happen, as the file exists!
651 # Cannot happen, as the file exists!
651 result.append(part)
652 result.append(part)
652 dir = os.path.join(dir, lpart)
653 dir = os.path.join(dir, lpart)
653
654
654 return ''.join(result)
655 return ''.join(result)
655
656
656 def checknlink(testfile):
657 def checknlink(testfile):
657 '''check whether hardlink count reporting works properly'''
658 '''check whether hardlink count reporting works properly'''
658
659
659 # testfile may be open, so we need a separate file for checking to
660 # testfile may be open, so we need a separate file for checking to
660 # work around issue2543 (or testfile may get lost on Samba shares)
661 # work around issue2543 (or testfile may get lost on Samba shares)
661 f1 = testfile + ".hgtmp1"
662 f1 = testfile + ".hgtmp1"
662 if os.path.lexists(f1):
663 if os.path.lexists(f1):
663 return False
664 return False
664 try:
665 try:
665 posixfile(f1, 'w').close()
666 posixfile(f1, 'w').close()
666 except IOError:
667 except IOError:
667 return False
668 return False
668
669
669 f2 = testfile + ".hgtmp2"
670 f2 = testfile + ".hgtmp2"
670 fd = None
671 fd = None
671 try:
672 try:
672 try:
673 try:
673 oslink(f1, f2)
674 oslink(f1, f2)
674 except OSError:
675 except OSError:
675 return False
676 return False
676
677
677 # nlinks() may behave differently for files on Windows shares if
678 # nlinks() may behave differently for files on Windows shares if
678 # the file is open.
679 # the file is open.
679 fd = posixfile(f2)
680 fd = posixfile(f2)
680 return nlinks(f2) > 1
681 return nlinks(f2) > 1
681 finally:
682 finally:
682 if fd is not None:
683 if fd is not None:
683 fd.close()
684 fd.close()
684 for f in (f1, f2):
685 for f in (f1, f2):
685 try:
686 try:
686 os.unlink(f)
687 os.unlink(f)
687 except OSError:
688 except OSError:
688 pass
689 pass
689
690
690 return False
691 return False
691
692
692 def endswithsep(path):
693 def endswithsep(path):
693 '''Check path ends with os.sep or os.altsep.'''
694 '''Check path ends with os.sep or os.altsep.'''
694 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
695 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
695
696
696 def splitpath(path):
697 def splitpath(path):
697 '''Split path by os.sep.
698 '''Split path by os.sep.
698 Note that this function does not use os.altsep because this is
699 Note that this function does not use os.altsep because this is
699 an alternative of simple "xxx.split(os.sep)".
700 an alternative of simple "xxx.split(os.sep)".
700 It is recommended to use os.path.normpath() before using this
701 It is recommended to use os.path.normpath() before using this
701 function if need.'''
702 function if need.'''
702 return path.split(os.sep)
703 return path.split(os.sep)
703
704
704 def gui():
705 def gui():
705 '''Are we running in a GUI?'''
706 '''Are we running in a GUI?'''
706 if sys.platform == 'darwin':
707 if sys.platform == 'darwin':
707 if 'SSH_CONNECTION' in os.environ:
708 if 'SSH_CONNECTION' in os.environ:
708 # handle SSH access to a box where the user is logged in
709 # handle SSH access to a box where the user is logged in
709 return False
710 return False
710 elif getattr(osutil, 'isgui', None):
711 elif getattr(osutil, 'isgui', None):
711 # check if a CoreGraphics session is available
712 # check if a CoreGraphics session is available
712 return osutil.isgui()
713 return osutil.isgui()
713 else:
714 else:
714 # pure build; use a safe default
715 # pure build; use a safe default
715 return True
716 return True
716 else:
717 else:
717 return os.name == "nt" or os.environ.get("DISPLAY")
718 return os.name == "nt" or os.environ.get("DISPLAY")
718
719
719 def mktempcopy(name, emptyok=False, createmode=None):
720 def mktempcopy(name, emptyok=False, createmode=None):
720 """Create a temporary file with the same contents from name
721 """Create a temporary file with the same contents from name
721
722
722 The permission bits are copied from the original file.
723 The permission bits are copied from the original file.
723
724
724 If the temporary file is going to be truncated immediately, you
725 If the temporary file is going to be truncated immediately, you
725 can use emptyok=True as an optimization.
726 can use emptyok=True as an optimization.
726
727
727 Returns the name of the temporary file.
728 Returns the name of the temporary file.
728 """
729 """
729 d, fn = os.path.split(name)
730 d, fn = os.path.split(name)
730 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
731 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
731 os.close(fd)
732 os.close(fd)
732 # Temporary files are created with mode 0600, which is usually not
733 # Temporary files are created with mode 0600, which is usually not
733 # what we want. If the original file already exists, just copy
734 # what we want. If the original file already exists, just copy
734 # its mode. Otherwise, manually obey umask.
735 # its mode. Otherwise, manually obey umask.
735 copymode(name, temp, createmode)
736 copymode(name, temp, createmode)
736 if emptyok:
737 if emptyok:
737 return temp
738 return temp
738 try:
739 try:
739 try:
740 try:
740 ifp = posixfile(name, "rb")
741 ifp = posixfile(name, "rb")
741 except IOError, inst:
742 except IOError, inst:
742 if inst.errno == errno.ENOENT:
743 if inst.errno == errno.ENOENT:
743 return temp
744 return temp
744 if not getattr(inst, 'filename', None):
745 if not getattr(inst, 'filename', None):
745 inst.filename = name
746 inst.filename = name
746 raise
747 raise
747 ofp = posixfile(temp, "wb")
748 ofp = posixfile(temp, "wb")
748 for chunk in filechunkiter(ifp):
749 for chunk in filechunkiter(ifp):
749 ofp.write(chunk)
750 ofp.write(chunk)
750 ifp.close()
751 ifp.close()
751 ofp.close()
752 ofp.close()
752 except:
753 except:
753 try: os.unlink(temp)
754 try: os.unlink(temp)
754 except: pass
755 except: pass
755 raise
756 raise
756 return temp
757 return temp
757
758
758 class atomictempfile(object):
759 class atomictempfile(object):
759 '''writeable file object that atomically updates a file
760 '''writeable file object that atomically updates a file
760
761
761 All writes will go to a temporary copy of the original file. Call
762 All writes will go to a temporary copy of the original file. Call
762 close() when you are done writing, and atomictempfile will rename
763 close() when you are done writing, and atomictempfile will rename
763 the temporary copy to the original name, making the changes
764 the temporary copy to the original name, making the changes
764 visible. If the object is destroyed without being closed, all your
765 visible. If the object is destroyed without being closed, all your
765 writes are discarded.
766 writes are discarded.
766 '''
767 '''
767 def __init__(self, name, mode='w+b', createmode=None):
768 def __init__(self, name, mode='w+b', createmode=None):
768 self.__name = name # permanent name
769 self.__name = name # permanent name
769 self._tempname = mktempcopy(name, emptyok=('w' in mode),
770 self._tempname = mktempcopy(name, emptyok=('w' in mode),
770 createmode=createmode)
771 createmode=createmode)
771 self._fp = posixfile(self._tempname, mode)
772 self._fp = posixfile(self._tempname, mode)
772
773
773 # delegated methods
774 # delegated methods
774 self.write = self._fp.write
775 self.write = self._fp.write
775 self.fileno = self._fp.fileno
776 self.fileno = self._fp.fileno
776
777
777 def close(self):
778 def close(self):
778 if not self._fp.closed:
779 if not self._fp.closed:
779 self._fp.close()
780 self._fp.close()
780 rename(self._tempname, localpath(self.__name))
781 rename(self._tempname, localpath(self.__name))
781
782
782 def discard(self):
783 def discard(self):
783 if not self._fp.closed:
784 if not self._fp.closed:
784 try:
785 try:
785 os.unlink(self._tempname)
786 os.unlink(self._tempname)
786 except OSError:
787 except OSError:
787 pass
788 pass
788 self._fp.close()
789 self._fp.close()
789
790
790 def __del__(self):
791 def __del__(self):
791 if safehasattr(self, '_fp'): # constructor actually did something
792 if safehasattr(self, '_fp'): # constructor actually did something
792 self.discard()
793 self.discard()
793
794
794 def makedirs(name, mode=None):
795 def makedirs(name, mode=None):
795 """recursive directory creation with parent mode inheritance"""
796 """recursive directory creation with parent mode inheritance"""
796 try:
797 try:
797 os.mkdir(name)
798 os.mkdir(name)
798 except OSError, err:
799 except OSError, err:
799 if err.errno == errno.EEXIST:
800 if err.errno == errno.EEXIST:
800 return
801 return
801 if err.errno != errno.ENOENT or not name:
802 if err.errno != errno.ENOENT or not name:
802 raise
803 raise
803 parent = os.path.dirname(os.path.abspath(name))
804 parent = os.path.dirname(os.path.abspath(name))
804 if parent == name:
805 if parent == name:
805 raise
806 raise
806 makedirs(parent, mode)
807 makedirs(parent, mode)
807 os.mkdir(name)
808 os.mkdir(name)
808 if mode is not None:
809 if mode is not None:
809 os.chmod(name, mode)
810 os.chmod(name, mode)
810
811
811 def readfile(path):
812 def readfile(path):
812 fp = open(path, 'rb')
813 fp = open(path, 'rb')
813 try:
814 try:
814 return fp.read()
815 return fp.read()
815 finally:
816 finally:
816 fp.close()
817 fp.close()
817
818
818 def writefile(path, text):
819 def writefile(path, text):
819 fp = open(path, 'wb')
820 fp = open(path, 'wb')
820 try:
821 try:
821 fp.write(text)
822 fp.write(text)
822 finally:
823 finally:
823 fp.close()
824 fp.close()
824
825
825 def appendfile(path, text):
826 def appendfile(path, text):
826 fp = open(path, 'ab')
827 fp = open(path, 'ab')
827 try:
828 try:
828 fp.write(text)
829 fp.write(text)
829 finally:
830 finally:
830 fp.close()
831 fp.close()
831
832
832 class chunkbuffer(object):
833 class chunkbuffer(object):
833 """Allow arbitrary sized chunks of data to be efficiently read from an
834 """Allow arbitrary sized chunks of data to be efficiently read from an
834 iterator over chunks of arbitrary size."""
835 iterator over chunks of arbitrary size."""
835
836
836 def __init__(self, in_iter):
837 def __init__(self, in_iter):
837 """in_iter is the iterator that's iterating over the input chunks.
838 """in_iter is the iterator that's iterating over the input chunks.
838 targetsize is how big a buffer to try to maintain."""
839 targetsize is how big a buffer to try to maintain."""
839 def splitbig(chunks):
840 def splitbig(chunks):
840 for chunk in chunks:
841 for chunk in chunks:
841 if len(chunk) > 2**20:
842 if len(chunk) > 2**20:
842 pos = 0
843 pos = 0
843 while pos < len(chunk):
844 while pos < len(chunk):
844 end = pos + 2 ** 18
845 end = pos + 2 ** 18
845 yield chunk[pos:end]
846 yield chunk[pos:end]
846 pos = end
847 pos = end
847 else:
848 else:
848 yield chunk
849 yield chunk
849 self.iter = splitbig(in_iter)
850 self.iter = splitbig(in_iter)
850 self._queue = []
851 self._queue = []
851
852
852 def read(self, l):
853 def read(self, l):
853 """Read L bytes of data from the iterator of chunks of data.
854 """Read L bytes of data from the iterator of chunks of data.
854 Returns less than L bytes if the iterator runs dry."""
855 Returns less than L bytes if the iterator runs dry."""
855 left = l
856 left = l
856 buf = ''
857 buf = ''
857 queue = self._queue
858 queue = self._queue
858 while left > 0:
859 while left > 0:
859 # refill the queue
860 # refill the queue
860 if not queue:
861 if not queue:
861 target = 2**18
862 target = 2**18
862 for chunk in self.iter:
863 for chunk in self.iter:
863 queue.append(chunk)
864 queue.append(chunk)
864 target -= len(chunk)
865 target -= len(chunk)
865 if target <= 0:
866 if target <= 0:
866 break
867 break
867 if not queue:
868 if not queue:
868 break
869 break
869
870
870 chunk = queue.pop(0)
871 chunk = queue.pop(0)
871 left -= len(chunk)
872 left -= len(chunk)
872 if left < 0:
873 if left < 0:
873 queue.insert(0, chunk[left:])
874 queue.insert(0, chunk[left:])
874 buf += chunk[:left]
875 buf += chunk[:left]
875 else:
876 else:
876 buf += chunk
877 buf += chunk
877
878
878 return buf
879 return buf
879
880
880 def filechunkiter(f, size=65536, limit=None):
881 def filechunkiter(f, size=65536, limit=None):
881 """Create a generator that produces the data in the file size
882 """Create a generator that produces the data in the file size
882 (default 65536) bytes at a time, up to optional limit (default is
883 (default 65536) bytes at a time, up to optional limit (default is
883 to read all data). Chunks may be less than size bytes if the
884 to read all data). Chunks may be less than size bytes if the
884 chunk is the last chunk in the file, or the file is a socket or
885 chunk is the last chunk in the file, or the file is a socket or
885 some other type of file that sometimes reads less data than is
886 some other type of file that sometimes reads less data than is
886 requested."""
887 requested."""
887 assert size >= 0
888 assert size >= 0
888 assert limit is None or limit >= 0
889 assert limit is None or limit >= 0
889 while True:
890 while True:
890 if limit is None:
891 if limit is None:
891 nbytes = size
892 nbytes = size
892 else:
893 else:
893 nbytes = min(limit, size)
894 nbytes = min(limit, size)
894 s = nbytes and f.read(nbytes)
895 s = nbytes and f.read(nbytes)
895 if not s:
896 if not s:
896 break
897 break
897 if limit:
898 if limit:
898 limit -= len(s)
899 limit -= len(s)
899 yield s
900 yield s
900
901
901 def makedate():
902 def makedate():
902 lt = time.localtime()
903 lt = time.localtime()
903 if lt[8] == 1 and time.daylight:
904 if lt[8] == 1 and time.daylight:
904 tz = time.altzone
905 tz = time.altzone
905 else:
906 else:
906 tz = time.timezone
907 tz = time.timezone
907 t = time.mktime(lt)
908 t = time.mktime(lt)
908 if t < 0:
909 if t < 0:
909 hint = _("check your clock")
910 hint = _("check your clock")
910 raise Abort(_("negative timestamp: %d") % t, hint=hint)
911 raise Abort(_("negative timestamp: %d") % t, hint=hint)
911 return t, tz
912 return t, tz
912
913
913 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
914 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
914 """represent a (unixtime, offset) tuple as a localized time.
915 """represent a (unixtime, offset) tuple as a localized time.
915 unixtime is seconds since the epoch, and offset is the time zone's
916 unixtime is seconds since the epoch, and offset is the time zone's
916 number of seconds away from UTC. if timezone is false, do not
917 number of seconds away from UTC. if timezone is false, do not
917 append time zone to string."""
918 append time zone to string."""
918 t, tz = date or makedate()
919 t, tz = date or makedate()
919 if t < 0:
920 if t < 0:
920 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
921 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
921 tz = 0
922 tz = 0
922 if "%1" in format or "%2" in format:
923 if "%1" in format or "%2" in format:
923 sign = (tz > 0) and "-" or "+"
924 sign = (tz > 0) and "-" or "+"
924 minutes = abs(tz) // 60
925 minutes = abs(tz) // 60
925 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
926 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
926 format = format.replace("%2", "%02d" % (minutes % 60))
927 format = format.replace("%2", "%02d" % (minutes % 60))
927 try:
928 try:
928 t = time.gmtime(float(t) - tz)
929 t = time.gmtime(float(t) - tz)
929 except ValueError:
930 except ValueError:
930 # time was out of range
931 # time was out of range
931 t = time.gmtime(sys.maxint)
932 t = time.gmtime(sys.maxint)
932 s = time.strftime(format, t)
933 s = time.strftime(format, t)
933 return s
934 return s
934
935
935 def shortdate(date=None):
936 def shortdate(date=None):
936 """turn (timestamp, tzoff) tuple into iso 8631 date."""
937 """turn (timestamp, tzoff) tuple into iso 8631 date."""
937 return datestr(date, format='%Y-%m-%d')
938 return datestr(date, format='%Y-%m-%d')
938
939
939 def strdate(string, format, defaults=[]):
940 def strdate(string, format, defaults=[]):
940 """parse a localized time string and return a (unixtime, offset) tuple.
941 """parse a localized time string and return a (unixtime, offset) tuple.
941 if the string cannot be parsed, ValueError is raised."""
942 if the string cannot be parsed, ValueError is raised."""
942 def timezone(string):
943 def timezone(string):
943 tz = string.split()[-1]
944 tz = string.split()[-1]
944 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
945 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
945 sign = (tz[0] == "+") and 1 or -1
946 sign = (tz[0] == "+") and 1 or -1
946 hours = int(tz[1:3])
947 hours = int(tz[1:3])
947 minutes = int(tz[3:5])
948 minutes = int(tz[3:5])
948 return -sign * (hours * 60 + minutes) * 60
949 return -sign * (hours * 60 + minutes) * 60
949 if tz == "GMT" or tz == "UTC":
950 if tz == "GMT" or tz == "UTC":
950 return 0
951 return 0
951 return None
952 return None
952
953
953 # NOTE: unixtime = localunixtime + offset
954 # NOTE: unixtime = localunixtime + offset
954 offset, date = timezone(string), string
955 offset, date = timezone(string), string
955 if offset is not None:
956 if offset is not None:
956 date = " ".join(string.split()[:-1])
957 date = " ".join(string.split()[:-1])
957
958
958 # add missing elements from defaults
959 # add missing elements from defaults
959 usenow = False # default to using biased defaults
960 usenow = False # default to using biased defaults
960 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
961 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
961 found = [True for p in part if ("%"+p) in format]
962 found = [True for p in part if ("%"+p) in format]
962 if not found:
963 if not found:
963 date += "@" + defaults[part][usenow]
964 date += "@" + defaults[part][usenow]
964 format += "@%" + part[0]
965 format += "@%" + part[0]
965 else:
966 else:
966 # We've found a specific time element, less specific time
967 # We've found a specific time element, less specific time
967 # elements are relative to today
968 # elements are relative to today
968 usenow = True
969 usenow = True
969
970
970 timetuple = time.strptime(date, format)
971 timetuple = time.strptime(date, format)
971 localunixtime = int(calendar.timegm(timetuple))
972 localunixtime = int(calendar.timegm(timetuple))
972 if offset is None:
973 if offset is None:
973 # local timezone
974 # local timezone
974 unixtime = int(time.mktime(timetuple))
975 unixtime = int(time.mktime(timetuple))
975 offset = unixtime - localunixtime
976 offset = unixtime - localunixtime
976 else:
977 else:
977 unixtime = localunixtime + offset
978 unixtime = localunixtime + offset
978 return unixtime, offset
979 return unixtime, offset
979
980
980 def parsedate(date, formats=None, bias={}):
981 def parsedate(date, formats=None, bias={}):
981 """parse a localized date/time and return a (unixtime, offset) tuple.
982 """parse a localized date/time and return a (unixtime, offset) tuple.
982
983
983 The date may be a "unixtime offset" string or in one of the specified
984 The date may be a "unixtime offset" string or in one of the specified
984 formats. If the date already is a (unixtime, offset) tuple, it is returned.
985 formats. If the date already is a (unixtime, offset) tuple, it is returned.
985 """
986 """
986 if not date:
987 if not date:
987 return 0, 0
988 return 0, 0
988 if isinstance(date, tuple) and len(date) == 2:
989 if isinstance(date, tuple) and len(date) == 2:
989 return date
990 return date
990 if not formats:
991 if not formats:
991 formats = defaultdateformats
992 formats = defaultdateformats
992 date = date.strip()
993 date = date.strip()
993 try:
994 try:
994 when, offset = map(int, date.split(' '))
995 when, offset = map(int, date.split(' '))
995 except ValueError:
996 except ValueError:
996 # fill out defaults
997 # fill out defaults
997 now = makedate()
998 now = makedate()
998 defaults = {}
999 defaults = {}
999 for part in ("d", "mb", "yY", "HI", "M", "S"):
1000 for part in ("d", "mb", "yY", "HI", "M", "S"):
1000 # this piece is for rounding the specific end of unknowns
1001 # this piece is for rounding the specific end of unknowns
1001 b = bias.get(part)
1002 b = bias.get(part)
1002 if b is None:
1003 if b is None:
1003 if part[0] in "HMS":
1004 if part[0] in "HMS":
1004 b = "00"
1005 b = "00"
1005 else:
1006 else:
1006 b = "0"
1007 b = "0"
1007
1008
1008 # this piece is for matching the generic end to today's date
1009 # this piece is for matching the generic end to today's date
1009 n = datestr(now, "%" + part[0])
1010 n = datestr(now, "%" + part[0])
1010
1011
1011 defaults[part] = (b, n)
1012 defaults[part] = (b, n)
1012
1013
1013 for format in formats:
1014 for format in formats:
1014 try:
1015 try:
1015 when, offset = strdate(date, format, defaults)
1016 when, offset = strdate(date, format, defaults)
1016 except (ValueError, OverflowError):
1017 except (ValueError, OverflowError):
1017 pass
1018 pass
1018 else:
1019 else:
1019 break
1020 break
1020 else:
1021 else:
1021 raise Abort(_('invalid date: %r') % date)
1022 raise Abort(_('invalid date: %r') % date)
1022 # validate explicit (probably user-specified) date and
1023 # validate explicit (probably user-specified) date and
1023 # time zone offset. values must fit in signed 32 bits for
1024 # time zone offset. values must fit in signed 32 bits for
1024 # current 32-bit linux runtimes. timezones go from UTC-12
1025 # current 32-bit linux runtimes. timezones go from UTC-12
1025 # to UTC+14
1026 # to UTC+14
1026 if abs(when) > 0x7fffffff:
1027 if abs(when) > 0x7fffffff:
1027 raise Abort(_('date exceeds 32 bits: %d') % when)
1028 raise Abort(_('date exceeds 32 bits: %d') % when)
1028 if when < 0:
1029 if when < 0:
1029 raise Abort(_('negative date value: %d') % when)
1030 raise Abort(_('negative date value: %d') % when)
1030 if offset < -50400 or offset > 43200:
1031 if offset < -50400 or offset > 43200:
1031 raise Abort(_('impossible time zone offset: %d') % offset)
1032 raise Abort(_('impossible time zone offset: %d') % offset)
1032 return when, offset
1033 return when, offset
1033
1034
1034 def matchdate(date):
1035 def matchdate(date):
1035 """Return a function that matches a given date match specifier
1036 """Return a function that matches a given date match specifier
1036
1037
1037 Formats include:
1038 Formats include:
1038
1039
1039 '{date}' match a given date to the accuracy provided
1040 '{date}' match a given date to the accuracy provided
1040
1041
1041 '<{date}' on or before a given date
1042 '<{date}' on or before a given date
1042
1043
1043 '>{date}' on or after a given date
1044 '>{date}' on or after a given date
1044
1045
1045 >>> p1 = parsedate("10:29:59")
1046 >>> p1 = parsedate("10:29:59")
1046 >>> p2 = parsedate("10:30:00")
1047 >>> p2 = parsedate("10:30:00")
1047 >>> p3 = parsedate("10:30:59")
1048 >>> p3 = parsedate("10:30:59")
1048 >>> p4 = parsedate("10:31:00")
1049 >>> p4 = parsedate("10:31:00")
1049 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1050 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1050 >>> f = matchdate("10:30")
1051 >>> f = matchdate("10:30")
1051 >>> f(p1[0])
1052 >>> f(p1[0])
1052 False
1053 False
1053 >>> f(p2[0])
1054 >>> f(p2[0])
1054 True
1055 True
1055 >>> f(p3[0])
1056 >>> f(p3[0])
1056 True
1057 True
1057 >>> f(p4[0])
1058 >>> f(p4[0])
1058 False
1059 False
1059 >>> f(p5[0])
1060 >>> f(p5[0])
1060 False
1061 False
1061 """
1062 """
1062
1063
1063 def lower(date):
1064 def lower(date):
1064 d = dict(mb="1", d="1")
1065 d = dict(mb="1", d="1")
1065 return parsedate(date, extendeddateformats, d)[0]
1066 return parsedate(date, extendeddateformats, d)[0]
1066
1067
1067 def upper(date):
1068 def upper(date):
1068 d = dict(mb="12", HI="23", M="59", S="59")
1069 d = dict(mb="12", HI="23", M="59", S="59")
1069 for days in ("31", "30", "29"):
1070 for days in ("31", "30", "29"):
1070 try:
1071 try:
1071 d["d"] = days
1072 d["d"] = days
1072 return parsedate(date, extendeddateformats, d)[0]
1073 return parsedate(date, extendeddateformats, d)[0]
1073 except:
1074 except:
1074 pass
1075 pass
1075 d["d"] = "28"
1076 d["d"] = "28"
1076 return parsedate(date, extendeddateformats, d)[0]
1077 return parsedate(date, extendeddateformats, d)[0]
1077
1078
1078 date = date.strip()
1079 date = date.strip()
1079
1080
1080 if not date:
1081 if not date:
1081 raise Abort(_("dates cannot consist entirely of whitespace"))
1082 raise Abort(_("dates cannot consist entirely of whitespace"))
1082 elif date[0] == "<":
1083 elif date[0] == "<":
1083 if not date[1:]:
1084 if not date[1:]:
1084 raise Abort(_("invalid day spec, use '<DATE'"))
1085 raise Abort(_("invalid day spec, use '<DATE'"))
1085 when = upper(date[1:])
1086 when = upper(date[1:])
1086 return lambda x: x <= when
1087 return lambda x: x <= when
1087 elif date[0] == ">":
1088 elif date[0] == ">":
1088 if not date[1:]:
1089 if not date[1:]:
1089 raise Abort(_("invalid day spec, use '>DATE'"))
1090 raise Abort(_("invalid day spec, use '>DATE'"))
1090 when = lower(date[1:])
1091 when = lower(date[1:])
1091 return lambda x: x >= when
1092 return lambda x: x >= when
1092 elif date[0] == "-":
1093 elif date[0] == "-":
1093 try:
1094 try:
1094 days = int(date[1:])
1095 days = int(date[1:])
1095 except ValueError:
1096 except ValueError:
1096 raise Abort(_("invalid day spec: %s") % date[1:])
1097 raise Abort(_("invalid day spec: %s") % date[1:])
1097 if days < 0:
1098 if days < 0:
1098 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1099 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1099 % date[1:])
1100 % date[1:])
1100 when = makedate()[0] - days * 3600 * 24
1101 when = makedate()[0] - days * 3600 * 24
1101 return lambda x: x >= when
1102 return lambda x: x >= when
1102 elif " to " in date:
1103 elif " to " in date:
1103 a, b = date.split(" to ")
1104 a, b = date.split(" to ")
1104 start, stop = lower(a), upper(b)
1105 start, stop = lower(a), upper(b)
1105 return lambda x: x >= start and x <= stop
1106 return lambda x: x >= start and x <= stop
1106 else:
1107 else:
1107 start, stop = lower(date), upper(date)
1108 start, stop = lower(date), upper(date)
1108 return lambda x: x >= start and x <= stop
1109 return lambda x: x >= start and x <= stop
1109
1110
1110 def shortuser(user):
1111 def shortuser(user):
1111 """Return a short representation of a user name or email address."""
1112 """Return a short representation of a user name or email address."""
1112 f = user.find('@')
1113 f = user.find('@')
1113 if f >= 0:
1114 if f >= 0:
1114 user = user[:f]
1115 user = user[:f]
1115 f = user.find('<')
1116 f = user.find('<')
1116 if f >= 0:
1117 if f >= 0:
1117 user = user[f + 1:]
1118 user = user[f + 1:]
1118 f = user.find(' ')
1119 f = user.find(' ')
1119 if f >= 0:
1120 if f >= 0:
1120 user = user[:f]
1121 user = user[:f]
1121 f = user.find('.')
1122 f = user.find('.')
1122 if f >= 0:
1123 if f >= 0:
1123 user = user[:f]
1124 user = user[:f]
1124 return user
1125 return user
1125
1126
1126 def email(author):
1127 def email(author):
1127 '''get email of author.'''
1128 '''get email of author.'''
1128 r = author.find('>')
1129 r = author.find('>')
1129 if r == -1:
1130 if r == -1:
1130 r = None
1131 r = None
1131 return author[author.find('<') + 1:r]
1132 return author[author.find('<') + 1:r]
1132
1133
1133 def _ellipsis(text, maxlength):
1134 def _ellipsis(text, maxlength):
1134 if len(text) <= maxlength:
1135 if len(text) <= maxlength:
1135 return text, False
1136 return text, False
1136 else:
1137 else:
1137 return "%s..." % (text[:maxlength - 3]), True
1138 return "%s..." % (text[:maxlength - 3]), True
1138
1139
1139 def ellipsis(text, maxlength=400):
1140 def ellipsis(text, maxlength=400):
1140 """Trim string to at most maxlength (default: 400) characters."""
1141 """Trim string to at most maxlength (default: 400) characters."""
1141 try:
1142 try:
1142 # use unicode not to split at intermediate multi-byte sequence
1143 # use unicode not to split at intermediate multi-byte sequence
1143 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1144 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1144 maxlength)
1145 maxlength)
1145 if not truncated:
1146 if not truncated:
1146 return text
1147 return text
1147 return utext.encode(encoding.encoding)
1148 return utext.encode(encoding.encoding)
1148 except (UnicodeDecodeError, UnicodeEncodeError):
1149 except (UnicodeDecodeError, UnicodeEncodeError):
1149 return _ellipsis(text, maxlength)[0]
1150 return _ellipsis(text, maxlength)[0]
1150
1151
1151 def bytecount(nbytes):
1152 def bytecount(nbytes):
1152 '''return byte count formatted as readable string, with units'''
1153 '''return byte count formatted as readable string, with units'''
1153
1154
1154 units = (
1155 units = (
1155 (100, 1 << 30, _('%.0f GB')),
1156 (100, 1 << 30, _('%.0f GB')),
1156 (10, 1 << 30, _('%.1f GB')),
1157 (10, 1 << 30, _('%.1f GB')),
1157 (1, 1 << 30, _('%.2f GB')),
1158 (1, 1 << 30, _('%.2f GB')),
1158 (100, 1 << 20, _('%.0f MB')),
1159 (100, 1 << 20, _('%.0f MB')),
1159 (10, 1 << 20, _('%.1f MB')),
1160 (10, 1 << 20, _('%.1f MB')),
1160 (1, 1 << 20, _('%.2f MB')),
1161 (1, 1 << 20, _('%.2f MB')),
1161 (100, 1 << 10, _('%.0f KB')),
1162 (100, 1 << 10, _('%.0f KB')),
1162 (10, 1 << 10, _('%.1f KB')),
1163 (10, 1 << 10, _('%.1f KB')),
1163 (1, 1 << 10, _('%.2f KB')),
1164 (1, 1 << 10, _('%.2f KB')),
1164 (1, 1, _('%.0f bytes')),
1165 (1, 1, _('%.0f bytes')),
1165 )
1166 )
1166
1167
1167 for multiplier, divisor, format in units:
1168 for multiplier, divisor, format in units:
1168 if nbytes >= divisor * multiplier:
1169 if nbytes >= divisor * multiplier:
1169 return format % (nbytes / float(divisor))
1170 return format % (nbytes / float(divisor))
1170 return units[-1][2] % nbytes
1171 return units[-1][2] % nbytes
1171
1172
1172 def uirepr(s):
1173 def uirepr(s):
1173 # Avoid double backslash in Windows path repr()
1174 # Avoid double backslash in Windows path repr()
1174 return repr(s).replace('\\\\', '\\')
1175 return repr(s).replace('\\\\', '\\')
1175
1176
1176 # delay import of textwrap
1177 # delay import of textwrap
1177 def MBTextWrapper(**kwargs):
1178 def MBTextWrapper(**kwargs):
1178 class tw(textwrap.TextWrapper):
1179 class tw(textwrap.TextWrapper):
1179 """
1180 """
1180 Extend TextWrapper for width-awareness.
1181 Extend TextWrapper for width-awareness.
1181
1182
1182 Neither number of 'bytes' in any encoding nor 'characters' is
1183 Neither number of 'bytes' in any encoding nor 'characters' is
1183 appropriate to calculate terminal columns for specified string.
1184 appropriate to calculate terminal columns for specified string.
1184
1185
1185 Original TextWrapper implementation uses built-in 'len()' directly,
1186 Original TextWrapper implementation uses built-in 'len()' directly,
1186 so overriding is needed to use width information of each characters.
1187 so overriding is needed to use width information of each characters.
1187
1188
1188 In addition, characters classified into 'ambiguous' width are
1189 In addition, characters classified into 'ambiguous' width are
1189 treated as wide in east asian area, but as narrow in other.
1190 treated as wide in east asian area, but as narrow in other.
1190
1191
1191 This requires use decision to determine width of such characters.
1192 This requires use decision to determine width of such characters.
1192 """
1193 """
1193 def __init__(self, **kwargs):
1194 def __init__(self, **kwargs):
1194 textwrap.TextWrapper.__init__(self, **kwargs)
1195 textwrap.TextWrapper.__init__(self, **kwargs)
1195
1196
1196 # for compatibility between 2.4 and 2.6
1197 # for compatibility between 2.4 and 2.6
1197 if getattr(self, 'drop_whitespace', None) is None:
1198 if getattr(self, 'drop_whitespace', None) is None:
1198 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1199 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1199
1200
1200 def _cutdown(self, ucstr, space_left):
1201 def _cutdown(self, ucstr, space_left):
1201 l = 0
1202 l = 0
1202 colwidth = encoding.ucolwidth
1203 colwidth = encoding.ucolwidth
1203 for i in xrange(len(ucstr)):
1204 for i in xrange(len(ucstr)):
1204 l += colwidth(ucstr[i])
1205 l += colwidth(ucstr[i])
1205 if space_left < l:
1206 if space_left < l:
1206 return (ucstr[:i], ucstr[i:])
1207 return (ucstr[:i], ucstr[i:])
1207 return ucstr, ''
1208 return ucstr, ''
1208
1209
1209 # overriding of base class
1210 # overriding of base class
1210 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1211 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1211 space_left = max(width - cur_len, 1)
1212 space_left = max(width - cur_len, 1)
1212
1213
1213 if self.break_long_words:
1214 if self.break_long_words:
1214 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1215 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1215 cur_line.append(cut)
1216 cur_line.append(cut)
1216 reversed_chunks[-1] = res
1217 reversed_chunks[-1] = res
1217 elif not cur_line:
1218 elif not cur_line:
1218 cur_line.append(reversed_chunks.pop())
1219 cur_line.append(reversed_chunks.pop())
1219
1220
1220 # this overriding code is imported from TextWrapper of python 2.6
1221 # this overriding code is imported from TextWrapper of python 2.6
1221 # to calculate columns of string by 'encoding.ucolwidth()'
1222 # to calculate columns of string by 'encoding.ucolwidth()'
1222 def _wrap_chunks(self, chunks):
1223 def _wrap_chunks(self, chunks):
1223 colwidth = encoding.ucolwidth
1224 colwidth = encoding.ucolwidth
1224
1225
1225 lines = []
1226 lines = []
1226 if self.width <= 0:
1227 if self.width <= 0:
1227 raise ValueError("invalid width %r (must be > 0)" % self.width)
1228 raise ValueError("invalid width %r (must be > 0)" % self.width)
1228
1229
1229 # Arrange in reverse order so items can be efficiently popped
1230 # Arrange in reverse order so items can be efficiently popped
1230 # from a stack of chucks.
1231 # from a stack of chucks.
1231 chunks.reverse()
1232 chunks.reverse()
1232
1233
1233 while chunks:
1234 while chunks:
1234
1235
1235 # Start the list of chunks that will make up the current line.
1236 # Start the list of chunks that will make up the current line.
1236 # cur_len is just the length of all the chunks in cur_line.
1237 # cur_len is just the length of all the chunks in cur_line.
1237 cur_line = []
1238 cur_line = []
1238 cur_len = 0
1239 cur_len = 0
1239
1240
1240 # Figure out which static string will prefix this line.
1241 # Figure out which static string will prefix this line.
1241 if lines:
1242 if lines:
1242 indent = self.subsequent_indent
1243 indent = self.subsequent_indent
1243 else:
1244 else:
1244 indent = self.initial_indent
1245 indent = self.initial_indent
1245
1246
1246 # Maximum width for this line.
1247 # Maximum width for this line.
1247 width = self.width - len(indent)
1248 width = self.width - len(indent)
1248
1249
1249 # First chunk on line is whitespace -- drop it, unless this
1250 # First chunk on line is whitespace -- drop it, unless this
1250 # is the very beginning of the text (ie. no lines started yet).
1251 # is the very beginning of the text (ie. no lines started yet).
1251 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1252 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1252 del chunks[-1]
1253 del chunks[-1]
1253
1254
1254 while chunks:
1255 while chunks:
1255 l = colwidth(chunks[-1])
1256 l = colwidth(chunks[-1])
1256
1257
1257 # Can at least squeeze this chunk onto the current line.
1258 # Can at least squeeze this chunk onto the current line.
1258 if cur_len + l <= width:
1259 if cur_len + l <= width:
1259 cur_line.append(chunks.pop())
1260 cur_line.append(chunks.pop())
1260 cur_len += l
1261 cur_len += l
1261
1262
1262 # Nope, this line is full.
1263 # Nope, this line is full.
1263 else:
1264 else:
1264 break
1265 break
1265
1266
1266 # The current line is full, and the next chunk is too big to
1267 # The current line is full, and the next chunk is too big to
1267 # fit on *any* line (not just this one).
1268 # fit on *any* line (not just this one).
1268 if chunks and colwidth(chunks[-1]) > width:
1269 if chunks and colwidth(chunks[-1]) > width:
1269 self._handle_long_word(chunks, cur_line, cur_len, width)
1270 self._handle_long_word(chunks, cur_line, cur_len, width)
1270
1271
1271 # If the last chunk on this line is all whitespace, drop it.
1272 # If the last chunk on this line is all whitespace, drop it.
1272 if (self.drop_whitespace and
1273 if (self.drop_whitespace and
1273 cur_line and cur_line[-1].strip() == ''):
1274 cur_line and cur_line[-1].strip() == ''):
1274 del cur_line[-1]
1275 del cur_line[-1]
1275
1276
1276 # Convert current line back to a string and store it in list
1277 # Convert current line back to a string and store it in list
1277 # of all lines (return value).
1278 # of all lines (return value).
1278 if cur_line:
1279 if cur_line:
1279 lines.append(indent + ''.join(cur_line))
1280 lines.append(indent + ''.join(cur_line))
1280
1281
1281 return lines
1282 return lines
1282
1283
1283 global MBTextWrapper
1284 global MBTextWrapper
1284 MBTextWrapper = tw
1285 MBTextWrapper = tw
1285 return tw(**kwargs)
1286 return tw(**kwargs)
1286
1287
1287 def wrap(line, width, initindent='', hangindent=''):
1288 def wrap(line, width, initindent='', hangindent=''):
1288 maxindent = max(len(hangindent), len(initindent))
1289 maxindent = max(len(hangindent), len(initindent))
1289 if width <= maxindent:
1290 if width <= maxindent:
1290 # adjust for weird terminal size
1291 # adjust for weird terminal size
1291 width = max(78, maxindent + 1)
1292 width = max(78, maxindent + 1)
1292 line = line.decode(encoding.encoding, encoding.encodingmode)
1293 line = line.decode(encoding.encoding, encoding.encodingmode)
1293 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1294 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1294 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1295 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1295 wrapper = MBTextWrapper(width=width,
1296 wrapper = MBTextWrapper(width=width,
1296 initial_indent=initindent,
1297 initial_indent=initindent,
1297 subsequent_indent=hangindent)
1298 subsequent_indent=hangindent)
1298 return wrapper.fill(line).encode(encoding.encoding)
1299 return wrapper.fill(line).encode(encoding.encoding)
1299
1300
1300 def iterlines(iterator):
1301 def iterlines(iterator):
1301 for chunk in iterator:
1302 for chunk in iterator:
1302 for line in chunk.splitlines():
1303 for line in chunk.splitlines():
1303 yield line
1304 yield line
1304
1305
1305 def expandpath(path):
1306 def expandpath(path):
1306 return os.path.expanduser(os.path.expandvars(path))
1307 return os.path.expanduser(os.path.expandvars(path))
1307
1308
1308 def hgcmd():
1309 def hgcmd():
1309 """Return the command used to execute current hg
1310 """Return the command used to execute current hg
1310
1311
1311 This is different from hgexecutable() because on Windows we want
1312 This is different from hgexecutable() because on Windows we want
1312 to avoid things opening new shell windows like batch files, so we
1313 to avoid things opening new shell windows like batch files, so we
1313 get either the python call or current executable.
1314 get either the python call or current executable.
1314 """
1315 """
1315 if mainfrozen():
1316 if mainfrozen():
1316 return [sys.executable]
1317 return [sys.executable]
1317 return gethgcmd()
1318 return gethgcmd()
1318
1319
1319 def rundetached(args, condfn):
1320 def rundetached(args, condfn):
1320 """Execute the argument list in a detached process.
1321 """Execute the argument list in a detached process.
1321
1322
1322 condfn is a callable which is called repeatedly and should return
1323 condfn is a callable which is called repeatedly and should return
1323 True once the child process is known to have started successfully.
1324 True once the child process is known to have started successfully.
1324 At this point, the child process PID is returned. If the child
1325 At this point, the child process PID is returned. If the child
1325 process fails to start or finishes before condfn() evaluates to
1326 process fails to start or finishes before condfn() evaluates to
1326 True, return -1.
1327 True, return -1.
1327 """
1328 """
1328 # Windows case is easier because the child process is either
1329 # Windows case is easier because the child process is either
1329 # successfully starting and validating the condition or exiting
1330 # successfully starting and validating the condition or exiting
1330 # on failure. We just poll on its PID. On Unix, if the child
1331 # on failure. We just poll on its PID. On Unix, if the child
1331 # process fails to start, it will be left in a zombie state until
1332 # process fails to start, it will be left in a zombie state until
1332 # the parent wait on it, which we cannot do since we expect a long
1333 # the parent wait on it, which we cannot do since we expect a long
1333 # running process on success. Instead we listen for SIGCHLD telling
1334 # running process on success. Instead we listen for SIGCHLD telling
1334 # us our child process terminated.
1335 # us our child process terminated.
1335 terminated = set()
1336 terminated = set()
1336 def handler(signum, frame):
1337 def handler(signum, frame):
1337 terminated.add(os.wait())
1338 terminated.add(os.wait())
1338 prevhandler = None
1339 prevhandler = None
1339 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1340 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1340 if SIGCHLD is not None:
1341 if SIGCHLD is not None:
1341 prevhandler = signal.signal(SIGCHLD, handler)
1342 prevhandler = signal.signal(SIGCHLD, handler)
1342 try:
1343 try:
1343 pid = spawndetached(args)
1344 pid = spawndetached(args)
1344 while not condfn():
1345 while not condfn():
1345 if ((pid in terminated or not testpid(pid))
1346 if ((pid in terminated or not testpid(pid))
1346 and not condfn()):
1347 and not condfn()):
1347 return -1
1348 return -1
1348 time.sleep(0.1)
1349 time.sleep(0.1)
1349 return pid
1350 return pid
1350 finally:
1351 finally:
1351 if prevhandler is not None:
1352 if prevhandler is not None:
1352 signal.signal(signal.SIGCHLD, prevhandler)
1353 signal.signal(signal.SIGCHLD, prevhandler)
1353
1354
1354 try:
1355 try:
1355 any, all = any, all
1356 any, all = any, all
1356 except NameError:
1357 except NameError:
1357 def any(iterable):
1358 def any(iterable):
1358 for i in iterable:
1359 for i in iterable:
1359 if i:
1360 if i:
1360 return True
1361 return True
1361 return False
1362 return False
1362
1363
1363 def all(iterable):
1364 def all(iterable):
1364 for i in iterable:
1365 for i in iterable:
1365 if not i:
1366 if not i:
1366 return False
1367 return False
1367 return True
1368 return True
1368
1369
1369 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1370 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1370 """Return the result of interpolating items in the mapping into string s.
1371 """Return the result of interpolating items in the mapping into string s.
1371
1372
1372 prefix is a single character string, or a two character string with
1373 prefix is a single character string, or a two character string with
1373 a backslash as the first character if the prefix needs to be escaped in
1374 a backslash as the first character if the prefix needs to be escaped in
1374 a regular expression.
1375 a regular expression.
1375
1376
1376 fn is an optional function that will be applied to the replacement text
1377 fn is an optional function that will be applied to the replacement text
1377 just before replacement.
1378 just before replacement.
1378
1379
1379 escape_prefix is an optional flag that allows using doubled prefix for
1380 escape_prefix is an optional flag that allows using doubled prefix for
1380 its escaping.
1381 its escaping.
1381 """
1382 """
1382 fn = fn or (lambda s: s)
1383 fn = fn or (lambda s: s)
1383 patterns = '|'.join(mapping.keys())
1384 patterns = '|'.join(mapping.keys())
1384 if escape_prefix:
1385 if escape_prefix:
1385 patterns += '|' + prefix
1386 patterns += '|' + prefix
1386 if len(prefix) > 1:
1387 if len(prefix) > 1:
1387 prefix_char = prefix[1:]
1388 prefix_char = prefix[1:]
1388 else:
1389 else:
1389 prefix_char = prefix
1390 prefix_char = prefix
1390 mapping[prefix_char] = prefix_char
1391 mapping[prefix_char] = prefix_char
1391 r = re.compile(r'%s(%s)' % (prefix, patterns))
1392 r = re.compile(r'%s(%s)' % (prefix, patterns))
1392 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1393 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1393
1394
1394 def getport(port):
1395 def getport(port):
1395 """Return the port for a given network service.
1396 """Return the port for a given network service.
1396
1397
1397 If port is an integer, it's returned as is. If it's a string, it's
1398 If port is an integer, it's returned as is. If it's a string, it's
1398 looked up using socket.getservbyname(). If there's no matching
1399 looked up using socket.getservbyname(). If there's no matching
1399 service, util.Abort is raised.
1400 service, util.Abort is raised.
1400 """
1401 """
1401 try:
1402 try:
1402 return int(port)
1403 return int(port)
1403 except ValueError:
1404 except ValueError:
1404 pass
1405 pass
1405
1406
1406 try:
1407 try:
1407 return socket.getservbyname(port)
1408 return socket.getservbyname(port)
1408 except socket.error:
1409 except socket.error:
1409 raise Abort(_("no port number associated with service '%s'") % port)
1410 raise Abort(_("no port number associated with service '%s'") % port)
1410
1411
1411 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1412 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1412 '0': False, 'no': False, 'false': False, 'off': False,
1413 '0': False, 'no': False, 'false': False, 'off': False,
1413 'never': False}
1414 'never': False}
1414
1415
1415 def parsebool(s):
1416 def parsebool(s):
1416 """Parse s into a boolean.
1417 """Parse s into a boolean.
1417
1418
1418 If s is not a valid boolean, returns None.
1419 If s is not a valid boolean, returns None.
1419 """
1420 """
1420 return _booleans.get(s.lower(), None)
1421 return _booleans.get(s.lower(), None)
1421
1422
1422 _hexdig = '0123456789ABCDEFabcdef'
1423 _hexdig = '0123456789ABCDEFabcdef'
1423 _hextochr = dict((a + b, chr(int(a + b, 16)))
1424 _hextochr = dict((a + b, chr(int(a + b, 16)))
1424 for a in _hexdig for b in _hexdig)
1425 for a in _hexdig for b in _hexdig)
1425
1426
1426 def _urlunquote(s):
1427 def _urlunquote(s):
1427 """unquote('abc%20def') -> 'abc def'."""
1428 """unquote('abc%20def') -> 'abc def'."""
1428 res = s.split('%')
1429 res = s.split('%')
1429 # fastpath
1430 # fastpath
1430 if len(res) == 1:
1431 if len(res) == 1:
1431 return s
1432 return s
1432 s = res[0]
1433 s = res[0]
1433 for item in res[1:]:
1434 for item in res[1:]:
1434 try:
1435 try:
1435 s += _hextochr[item[:2]] + item[2:]
1436 s += _hextochr[item[:2]] + item[2:]
1436 except KeyError:
1437 except KeyError:
1437 s += '%' + item
1438 s += '%' + item
1438 except UnicodeDecodeError:
1439 except UnicodeDecodeError:
1439 s += unichr(int(item[:2], 16)) + item[2:]
1440 s += unichr(int(item[:2], 16)) + item[2:]
1440 return s
1441 return s
1441
1442
1442 class url(object):
1443 class url(object):
1443 r"""Reliable URL parser.
1444 r"""Reliable URL parser.
1444
1445
1445 This parses URLs and provides attributes for the following
1446 This parses URLs and provides attributes for the following
1446 components:
1447 components:
1447
1448
1448 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1449 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1449
1450
1450 Missing components are set to None. The only exception is
1451 Missing components are set to None. The only exception is
1451 fragment, which is set to '' if present but empty.
1452 fragment, which is set to '' if present but empty.
1452
1453
1453 If parsefragment is False, fragment is included in query. If
1454 If parsefragment is False, fragment is included in query. If
1454 parsequery is False, query is included in path. If both are
1455 parsequery is False, query is included in path. If both are
1455 False, both fragment and query are included in path.
1456 False, both fragment and query are included in path.
1456
1457
1457 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1458 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1458
1459
1459 Note that for backward compatibility reasons, bundle URLs do not
1460 Note that for backward compatibility reasons, bundle URLs do not
1460 take host names. That means 'bundle://../' has a path of '../'.
1461 take host names. That means 'bundle://../' has a path of '../'.
1461
1462
1462 Examples:
1463 Examples:
1463
1464
1464 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1465 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1465 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1466 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1466 >>> url('ssh://[::1]:2200//home/joe/repo')
1467 >>> url('ssh://[::1]:2200//home/joe/repo')
1467 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1468 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1468 >>> url('file:///home/joe/repo')
1469 >>> url('file:///home/joe/repo')
1469 <url scheme: 'file', path: '/home/joe/repo'>
1470 <url scheme: 'file', path: '/home/joe/repo'>
1470 >>> url('file:///c:/temp/foo/')
1471 >>> url('file:///c:/temp/foo/')
1471 <url scheme: 'file', path: 'c:/temp/foo/'>
1472 <url scheme: 'file', path: 'c:/temp/foo/'>
1472 >>> url('bundle:foo')
1473 >>> url('bundle:foo')
1473 <url scheme: 'bundle', path: 'foo'>
1474 <url scheme: 'bundle', path: 'foo'>
1474 >>> url('bundle://../foo')
1475 >>> url('bundle://../foo')
1475 <url scheme: 'bundle', path: '../foo'>
1476 <url scheme: 'bundle', path: '../foo'>
1476 >>> url(r'c:\foo\bar')
1477 >>> url(r'c:\foo\bar')
1477 <url path: 'c:\\foo\\bar'>
1478 <url path: 'c:\\foo\\bar'>
1478 >>> url(r'\\blah\blah\blah')
1479 >>> url(r'\\blah\blah\blah')
1479 <url path: '\\\\blah\\blah\\blah'>
1480 <url path: '\\\\blah\\blah\\blah'>
1480 >>> url(r'\\blah\blah\blah#baz')
1481 >>> url(r'\\blah\blah\blah#baz')
1481 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1482 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1482
1483
1483 Authentication credentials:
1484 Authentication credentials:
1484
1485
1485 >>> url('ssh://joe:xyz@x/repo')
1486 >>> url('ssh://joe:xyz@x/repo')
1486 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1487 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1487 >>> url('ssh://joe@x/repo')
1488 >>> url('ssh://joe@x/repo')
1488 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1489 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1489
1490
1490 Query strings and fragments:
1491 Query strings and fragments:
1491
1492
1492 >>> url('http://host/a?b#c')
1493 >>> url('http://host/a?b#c')
1493 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1494 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1494 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1495 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1495 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1496 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1496 """
1497 """
1497
1498
1498 _safechars = "!~*'()+"
1499 _safechars = "!~*'()+"
1499 _safepchars = "/!~*'()+:"
1500 _safepchars = "/!~*'()+:"
1500 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1501 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1501
1502
1502 def __init__(self, path, parsequery=True, parsefragment=True):
1503 def __init__(self, path, parsequery=True, parsefragment=True):
1503 # We slowly chomp away at path until we have only the path left
1504 # We slowly chomp away at path until we have only the path left
1504 self.scheme = self.user = self.passwd = self.host = None
1505 self.scheme = self.user = self.passwd = self.host = None
1505 self.port = self.path = self.query = self.fragment = None
1506 self.port = self.path = self.query = self.fragment = None
1506 self._localpath = True
1507 self._localpath = True
1507 self._hostport = ''
1508 self._hostport = ''
1508 self._origpath = path
1509 self._origpath = path
1509
1510
1510 if parsefragment and '#' in path:
1511 if parsefragment and '#' in path:
1511 path, self.fragment = path.split('#', 1)
1512 path, self.fragment = path.split('#', 1)
1512 if not path:
1513 if not path:
1513 path = None
1514 path = None
1514
1515
1515 # special case for Windows drive letters and UNC paths
1516 # special case for Windows drive letters and UNC paths
1516 if hasdriveletter(path) or path.startswith(r'\\'):
1517 if hasdriveletter(path) or path.startswith(r'\\'):
1517 self.path = path
1518 self.path = path
1518 return
1519 return
1519
1520
1520 # For compatibility reasons, we can't handle bundle paths as
1521 # For compatibility reasons, we can't handle bundle paths as
1521 # normal URLS
1522 # normal URLS
1522 if path.startswith('bundle:'):
1523 if path.startswith('bundle:'):
1523 self.scheme = 'bundle'
1524 self.scheme = 'bundle'
1524 path = path[7:]
1525 path = path[7:]
1525 if path.startswith('//'):
1526 if path.startswith('//'):
1526 path = path[2:]
1527 path = path[2:]
1527 self.path = path
1528 self.path = path
1528 return
1529 return
1529
1530
1530 if self._matchscheme(path):
1531 if self._matchscheme(path):
1531 parts = path.split(':', 1)
1532 parts = path.split(':', 1)
1532 if parts[0]:
1533 if parts[0]:
1533 self.scheme, path = parts
1534 self.scheme, path = parts
1534 self._localpath = False
1535 self._localpath = False
1535
1536
1536 if not path:
1537 if not path:
1537 path = None
1538 path = None
1538 if self._localpath:
1539 if self._localpath:
1539 self.path = ''
1540 self.path = ''
1540 return
1541 return
1541 else:
1542 else:
1542 if self._localpath:
1543 if self._localpath:
1543 self.path = path
1544 self.path = path
1544 return
1545 return
1545
1546
1546 if parsequery and '?' in path:
1547 if parsequery and '?' in path:
1547 path, self.query = path.split('?', 1)
1548 path, self.query = path.split('?', 1)
1548 if not path:
1549 if not path:
1549 path = None
1550 path = None
1550 if not self.query:
1551 if not self.query:
1551 self.query = None
1552 self.query = None
1552
1553
1553 # // is required to specify a host/authority
1554 # // is required to specify a host/authority
1554 if path and path.startswith('//'):
1555 if path and path.startswith('//'):
1555 parts = path[2:].split('/', 1)
1556 parts = path[2:].split('/', 1)
1556 if len(parts) > 1:
1557 if len(parts) > 1:
1557 self.host, path = parts
1558 self.host, path = parts
1558 path = path
1559 path = path
1559 else:
1560 else:
1560 self.host = parts[0]
1561 self.host = parts[0]
1561 path = None
1562 path = None
1562 if not self.host:
1563 if not self.host:
1563 self.host = None
1564 self.host = None
1564 # path of file:///d is /d
1565 # path of file:///d is /d
1565 # path of file:///d:/ is d:/, not /d:/
1566 # path of file:///d:/ is d:/, not /d:/
1566 if path and not hasdriveletter(path):
1567 if path and not hasdriveletter(path):
1567 path = '/' + path
1568 path = '/' + path
1568
1569
1569 if self.host and '@' in self.host:
1570 if self.host and '@' in self.host:
1570 self.user, self.host = self.host.rsplit('@', 1)
1571 self.user, self.host = self.host.rsplit('@', 1)
1571 if ':' in self.user:
1572 if ':' in self.user:
1572 self.user, self.passwd = self.user.split(':', 1)
1573 self.user, self.passwd = self.user.split(':', 1)
1573 if not self.host:
1574 if not self.host:
1574 self.host = None
1575 self.host = None
1575
1576
1576 # Don't split on colons in IPv6 addresses without ports
1577 # Don't split on colons in IPv6 addresses without ports
1577 if (self.host and ':' in self.host and
1578 if (self.host and ':' in self.host and
1578 not (self.host.startswith('[') and self.host.endswith(']'))):
1579 not (self.host.startswith('[') and self.host.endswith(']'))):
1579 self._hostport = self.host
1580 self._hostport = self.host
1580 self.host, self.port = self.host.rsplit(':', 1)
1581 self.host, self.port = self.host.rsplit(':', 1)
1581 if not self.host:
1582 if not self.host:
1582 self.host = None
1583 self.host = None
1583
1584
1584 if (self.host and self.scheme == 'file' and
1585 if (self.host and self.scheme == 'file' and
1585 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1586 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1586 raise Abort(_('file:// URLs can only refer to localhost'))
1587 raise Abort(_('file:// URLs can only refer to localhost'))
1587
1588
1588 self.path = path
1589 self.path = path
1589
1590
1590 # leave the query string escaped
1591 # leave the query string escaped
1591 for a in ('user', 'passwd', 'host', 'port',
1592 for a in ('user', 'passwd', 'host', 'port',
1592 'path', 'fragment'):
1593 'path', 'fragment'):
1593 v = getattr(self, a)
1594 v = getattr(self, a)
1594 if v is not None:
1595 if v is not None:
1595 setattr(self, a, _urlunquote(v))
1596 setattr(self, a, _urlunquote(v))
1596
1597
1597 def __repr__(self):
1598 def __repr__(self):
1598 attrs = []
1599 attrs = []
1599 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1600 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1600 'query', 'fragment'):
1601 'query', 'fragment'):
1601 v = getattr(self, a)
1602 v = getattr(self, a)
1602 if v is not None:
1603 if v is not None:
1603 attrs.append('%s: %r' % (a, v))
1604 attrs.append('%s: %r' % (a, v))
1604 return '<url %s>' % ', '.join(attrs)
1605 return '<url %s>' % ', '.join(attrs)
1605
1606
1606 def __str__(self):
1607 def __str__(self):
1607 r"""Join the URL's components back into a URL string.
1608 r"""Join the URL's components back into a URL string.
1608
1609
1609 Examples:
1610 Examples:
1610
1611
1611 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1612 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1612 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1613 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1613 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1614 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1614 'http://user:pw@host:80/?foo=bar&baz=42'
1615 'http://user:pw@host:80/?foo=bar&baz=42'
1615 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1616 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1616 'http://user:pw@host:80/?foo=bar%3dbaz'
1617 'http://user:pw@host:80/?foo=bar%3dbaz'
1617 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1618 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1618 'ssh://user:pw@[::1]:2200//home/joe#'
1619 'ssh://user:pw@[::1]:2200//home/joe#'
1619 >>> str(url('http://localhost:80//'))
1620 >>> str(url('http://localhost:80//'))
1620 'http://localhost:80//'
1621 'http://localhost:80//'
1621 >>> str(url('http://localhost:80/'))
1622 >>> str(url('http://localhost:80/'))
1622 'http://localhost:80/'
1623 'http://localhost:80/'
1623 >>> str(url('http://localhost:80'))
1624 >>> str(url('http://localhost:80'))
1624 'http://localhost:80/'
1625 'http://localhost:80/'
1625 >>> str(url('bundle:foo'))
1626 >>> str(url('bundle:foo'))
1626 'bundle:foo'
1627 'bundle:foo'
1627 >>> str(url('bundle://../foo'))
1628 >>> str(url('bundle://../foo'))
1628 'bundle:../foo'
1629 'bundle:../foo'
1629 >>> str(url('path'))
1630 >>> str(url('path'))
1630 'path'
1631 'path'
1631 >>> str(url('file:///tmp/foo/bar'))
1632 >>> str(url('file:///tmp/foo/bar'))
1632 'file:///tmp/foo/bar'
1633 'file:///tmp/foo/bar'
1633 >>> print url(r'bundle:foo\bar')
1634 >>> print url(r'bundle:foo\bar')
1634 bundle:foo\bar
1635 bundle:foo\bar
1635 """
1636 """
1636 if self._localpath:
1637 if self._localpath:
1637 s = self.path
1638 s = self.path
1638 if self.scheme == 'bundle':
1639 if self.scheme == 'bundle':
1639 s = 'bundle:' + s
1640 s = 'bundle:' + s
1640 if self.fragment:
1641 if self.fragment:
1641 s += '#' + self.fragment
1642 s += '#' + self.fragment
1642 return s
1643 return s
1643
1644
1644 s = self.scheme + ':'
1645 s = self.scheme + ':'
1645 if self.user or self.passwd or self.host:
1646 if self.user or self.passwd or self.host:
1646 s += '//'
1647 s += '//'
1647 elif self.scheme and (not self.path or self.path.startswith('/')):
1648 elif self.scheme and (not self.path or self.path.startswith('/')):
1648 s += '//'
1649 s += '//'
1649 if self.user:
1650 if self.user:
1650 s += urllib.quote(self.user, safe=self._safechars)
1651 s += urllib.quote(self.user, safe=self._safechars)
1651 if self.passwd:
1652 if self.passwd:
1652 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1653 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1653 if self.user or self.passwd:
1654 if self.user or self.passwd:
1654 s += '@'
1655 s += '@'
1655 if self.host:
1656 if self.host:
1656 if not (self.host.startswith('[') and self.host.endswith(']')):
1657 if not (self.host.startswith('[') and self.host.endswith(']')):
1657 s += urllib.quote(self.host)
1658 s += urllib.quote(self.host)
1658 else:
1659 else:
1659 s += self.host
1660 s += self.host
1660 if self.port:
1661 if self.port:
1661 s += ':' + urllib.quote(self.port)
1662 s += ':' + urllib.quote(self.port)
1662 if self.host:
1663 if self.host:
1663 s += '/'
1664 s += '/'
1664 if self.path:
1665 if self.path:
1665 # TODO: similar to the query string, we should not unescape the
1666 # TODO: similar to the query string, we should not unescape the
1666 # path when we store it, the path might contain '%2f' = '/',
1667 # path when we store it, the path might contain '%2f' = '/',
1667 # which we should *not* escape.
1668 # which we should *not* escape.
1668 s += urllib.quote(self.path, safe=self._safepchars)
1669 s += urllib.quote(self.path, safe=self._safepchars)
1669 if self.query:
1670 if self.query:
1670 # we store the query in escaped form.
1671 # we store the query in escaped form.
1671 s += '?' + self.query
1672 s += '?' + self.query
1672 if self.fragment is not None:
1673 if self.fragment is not None:
1673 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1674 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1674 return s
1675 return s
1675
1676
1676 def authinfo(self):
1677 def authinfo(self):
1677 user, passwd = self.user, self.passwd
1678 user, passwd = self.user, self.passwd
1678 try:
1679 try:
1679 self.user, self.passwd = None, None
1680 self.user, self.passwd = None, None
1680 s = str(self)
1681 s = str(self)
1681 finally:
1682 finally:
1682 self.user, self.passwd = user, passwd
1683 self.user, self.passwd = user, passwd
1683 if not self.user:
1684 if not self.user:
1684 return (s, None)
1685 return (s, None)
1685 # authinfo[1] is passed to urllib2 password manager, and its
1686 # authinfo[1] is passed to urllib2 password manager, and its
1686 # URIs must not contain credentials. The host is passed in the
1687 # URIs must not contain credentials. The host is passed in the
1687 # URIs list because Python < 2.4.3 uses only that to search for
1688 # URIs list because Python < 2.4.3 uses only that to search for
1688 # a password.
1689 # a password.
1689 return (s, (None, (s, self.host),
1690 return (s, (None, (s, self.host),
1690 self.user, self.passwd or ''))
1691 self.user, self.passwd or ''))
1691
1692
1692 def isabs(self):
1693 def isabs(self):
1693 if self.scheme and self.scheme != 'file':
1694 if self.scheme and self.scheme != 'file':
1694 return True # remote URL
1695 return True # remote URL
1695 if hasdriveletter(self.path):
1696 if hasdriveletter(self.path):
1696 return True # absolute for our purposes - can't be joined()
1697 return True # absolute for our purposes - can't be joined()
1697 if self.path.startswith(r'\\'):
1698 if self.path.startswith(r'\\'):
1698 return True # Windows UNC path
1699 return True # Windows UNC path
1699 if self.path.startswith('/'):
1700 if self.path.startswith('/'):
1700 return True # POSIX-style
1701 return True # POSIX-style
1701 return False
1702 return False
1702
1703
1703 def localpath(self):
1704 def localpath(self):
1704 if self.scheme == 'file' or self.scheme == 'bundle':
1705 if self.scheme == 'file' or self.scheme == 'bundle':
1705 path = self.path or '/'
1706 path = self.path or '/'
1706 # For Windows, we need to promote hosts containing drive
1707 # For Windows, we need to promote hosts containing drive
1707 # letters to paths with drive letters.
1708 # letters to paths with drive letters.
1708 if hasdriveletter(self._hostport):
1709 if hasdriveletter(self._hostport):
1709 path = self._hostport + '/' + self.path
1710 path = self._hostport + '/' + self.path
1710 elif self.host is not None and self.path:
1711 elif self.host is not None and self.path:
1711 path = '/' + path
1712 path = '/' + path
1712 return path
1713 return path
1713 return self._origpath
1714 return self._origpath
1714
1715
1715 def hasscheme(path):
1716 def hasscheme(path):
1716 return bool(url(path).scheme)
1717 return bool(url(path).scheme)
1717
1718
1718 def hasdriveletter(path):
1719 def hasdriveletter(path):
1719 return path[1:2] == ':' and path[0:1].isalpha()
1720 return path[1:2] == ':' and path[0:1].isalpha()
1720
1721
1721 def urllocalpath(path):
1722 def urllocalpath(path):
1722 return url(path, parsequery=False, parsefragment=False).localpath()
1723 return url(path, parsequery=False, parsefragment=False).localpath()
1723
1724
1724 def hidepassword(u):
1725 def hidepassword(u):
1725 '''hide user credential in a url string'''
1726 '''hide user credential in a url string'''
1726 u = url(u)
1727 u = url(u)
1727 if u.passwd:
1728 if u.passwd:
1728 u.passwd = '***'
1729 u.passwd = '***'
1729 return str(u)
1730 return str(u)
1730
1731
1731 def removeauth(u):
1732 def removeauth(u):
1732 '''remove all authentication information from a url string'''
1733 '''remove all authentication information from a url string'''
1733 u = url(u)
1734 u = url(u)
1734 u.user = u.passwd = None
1735 u.user = u.passwd = None
1735 return str(u)
1736 return str(u)
1736
1737
1737 def isatty(fd):
1738 def isatty(fd):
1738 try:
1739 try:
1739 return fd.isatty()
1740 return fd.isatty()
1740 except AttributeError:
1741 except AttributeError:
1741 return False
1742 return False
@@ -1,314 +1,316 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import osutil
9 import osutil
10 import errno, msvcrt, os, re, sys
10 import errno, msvcrt, os, re, sys
11
11
12 import win32
12 import win32
13 executablepath = win32.executablepath
13 executablepath = win32.executablepath
14 getuser = win32.getuser
14 getuser = win32.getuser
15 hidewindow = win32.hidewindow
15 hidewindow = win32.hidewindow
16 lookupreg = win32.lookupreg
16 lookupreg = win32.lookupreg
17 makedir = win32.makedir
17 makedir = win32.makedir
18 nlinks = win32.nlinks
18 nlinks = win32.nlinks
19 oslink = win32.oslink
19 oslink = win32.oslink
20 samedevice = win32.samedevice
20 samedevice = win32.samedevice
21 samefile = win32.samefile
21 samefile = win32.samefile
22 setsignalhandler = win32.setsignalhandler
22 setsignalhandler = win32.setsignalhandler
23 spawndetached = win32.spawndetached
23 spawndetached = win32.spawndetached
24 termwidth = win32.termwidth
24 termwidth = win32.termwidth
25 testpid = win32.testpid
25 testpid = win32.testpid
26 unlink = win32.unlink
26 unlink = win32.unlink
27
27
28 nulldev = 'NUL:'
28 nulldev = 'NUL:'
29 umask = 0022
29 umask = 0022
30
30
31 # wrap osutil.posixfile to provide friendlier exceptions
31 # wrap osutil.posixfile to provide friendlier exceptions
32 def posixfile(name, mode='r', buffering=-1):
32 def posixfile(name, mode='r', buffering=-1):
33 try:
33 try:
34 return osutil.posixfile(name, mode, buffering)
34 return osutil.posixfile(name, mode, buffering)
35 except WindowsError, err:
35 except WindowsError, err:
36 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
36 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
37 posixfile.__doc__ = osutil.posixfile.__doc__
37 posixfile.__doc__ = osutil.posixfile.__doc__
38
38
39 class winstdout(object):
39 class winstdout(object):
40 '''stdout on windows misbehaves if sent through a pipe'''
40 '''stdout on windows misbehaves if sent through a pipe'''
41
41
42 def __init__(self, fp):
42 def __init__(self, fp):
43 self.fp = fp
43 self.fp = fp
44
44
45 def __getattr__(self, key):
45 def __getattr__(self, key):
46 return getattr(self.fp, key)
46 return getattr(self.fp, key)
47
47
48 def close(self):
48 def close(self):
49 try:
49 try:
50 self.fp.close()
50 self.fp.close()
51 except IOError:
51 except IOError:
52 pass
52 pass
53
53
54 def write(self, s):
54 def write(self, s):
55 try:
55 try:
56 # This is workaround for "Not enough space" error on
56 # This is workaround for "Not enough space" error on
57 # writing large size of data to console.
57 # writing large size of data to console.
58 limit = 16000
58 limit = 16000
59 l = len(s)
59 l = len(s)
60 start = 0
60 start = 0
61 self.softspace = 0
61 self.softspace = 0
62 while start < l:
62 while start < l:
63 end = start + limit
63 end = start + limit
64 self.fp.write(s[start:end])
64 self.fp.write(s[start:end])
65 start = end
65 start = end
66 except IOError, inst:
66 except IOError, inst:
67 if inst.errno != 0:
67 if inst.errno != 0:
68 raise
68 raise
69 self.close()
69 self.close()
70 raise IOError(errno.EPIPE, 'Broken pipe')
70 raise IOError(errno.EPIPE, 'Broken pipe')
71
71
72 def flush(self):
72 def flush(self):
73 try:
73 try:
74 return self.fp.flush()
74 return self.fp.flush()
75 except IOError, inst:
75 except IOError, inst:
76 if inst.errno != errno.EINVAL:
76 if inst.errno != errno.EINVAL:
77 raise
77 raise
78 self.close()
78 self.close()
79 raise IOError(errno.EPIPE, 'Broken pipe')
79 raise IOError(errno.EPIPE, 'Broken pipe')
80
80
81 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
81 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
82
82
83 def _is_win_9x():
83 def _is_win_9x():
84 '''return true if run on windows 95, 98 or me.'''
84 '''return true if run on windows 95, 98 or me.'''
85 try:
85 try:
86 return sys.getwindowsversion()[3] == 1
86 return sys.getwindowsversion()[3] == 1
87 except AttributeError:
87 except AttributeError:
88 return 'command' in os.environ.get('comspec', '')
88 return 'command' in os.environ.get('comspec', '')
89
89
90 def openhardlinks():
90 def openhardlinks():
91 return not _is_win_9x()
91 return not _is_win_9x()
92
92
93 def parsepatchoutput(output_line):
93 def parsepatchoutput(output_line):
94 """parses the output produced by patch and returns the filename"""
94 """parses the output produced by patch and returns the filename"""
95 pf = output_line[14:]
95 pf = output_line[14:]
96 if pf[0] == '`':
96 if pf[0] == '`':
97 pf = pf[1:-1] # Remove the quotes
97 pf = pf[1:-1] # Remove the quotes
98 return pf
98 return pf
99
99
100 def sshargs(sshcmd, host, user, port):
100 def sshargs(sshcmd, host, user, port):
101 '''Build argument list for ssh or Plink'''
101 '''Build argument list for ssh or Plink'''
102 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
102 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
103 args = user and ("%s@%s" % (user, host)) or host
103 args = user and ("%s@%s" % (user, host)) or host
104 return port and ("%s %s %s" % (args, pflag, port)) or args
104 return port and ("%s %s %s" % (args, pflag, port)) or args
105
105
106 def setflags(f, l, x):
106 def setflags(f, l, x):
107 pass
107 pass
108
108
109 def copymode(src, dst, mode=None):
109 def copymode(src, dst, mode=None):
110 pass
110 pass
111
111
112 def checkexec(path):
112 def checkexec(path):
113 return False
113 return False
114
114
115 def checklink(path):
115 def checklink(path):
116 return False
116 return False
117
117
118 def setbinary(fd):
118 def setbinary(fd):
119 # When run without console, pipes may expose invalid
119 # When run without console, pipes may expose invalid
120 # fileno(), usually set to -1.
120 # fileno(), usually set to -1.
121 fno = getattr(fd, 'fileno', None)
121 fno = getattr(fd, 'fileno', None)
122 if fno is not None and fno() >= 0:
122 if fno is not None and fno() >= 0:
123 msvcrt.setmode(fno(), os.O_BINARY)
123 msvcrt.setmode(fno(), os.O_BINARY)
124
124
125 def pconvert(path):
125 def pconvert(path):
126 return '/'.join(path.split(os.sep))
126 return '/'.join(path.split(os.sep))
127
127
128 def localpath(path):
128 def localpath(path):
129 return path.replace('/', '\\')
129 return path.replace('/', '\\')
130
130
131 def normpath(path):
131 def normpath(path):
132 return pconvert(os.path.normpath(path))
132 return pconvert(os.path.normpath(path))
133
133
134 normcase = os.path.normcase
135
134 def realpath(path):
136 def realpath(path):
135 '''
137 '''
136 Returns the true, canonical file system path equivalent to the given
138 Returns the true, canonical file system path equivalent to the given
137 path.
139 path.
138 '''
140 '''
139 # TODO: There may be a more clever way to do this that also handles other,
141 # TODO: There may be a more clever way to do this that also handles other,
140 # less common file systems.
142 # less common file systems.
141 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
143 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
142
144
143 def samestat(s1, s2):
145 def samestat(s1, s2):
144 return False
146 return False
145
147
146 # A sequence of backslashes is special iff it precedes a double quote:
148 # A sequence of backslashes is special iff it precedes a double quote:
147 # - if there's an even number of backslashes, the double quote is not
149 # - if there's an even number of backslashes, the double quote is not
148 # quoted (i.e. it ends the quoted region)
150 # quoted (i.e. it ends the quoted region)
149 # - if there's an odd number of backslashes, the double quote is quoted
151 # - if there's an odd number of backslashes, the double quote is quoted
150 # - in both cases, every pair of backslashes is unquoted into a single
152 # - in both cases, every pair of backslashes is unquoted into a single
151 # backslash
153 # backslash
152 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
154 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
153 # So, to quote a string, we must surround it in double quotes, double
155 # So, to quote a string, we must surround it in double quotes, double
154 # the number of backslashes that preceed double quotes and add another
156 # the number of backslashes that preceed double quotes and add another
155 # backslash before every double quote (being careful with the double
157 # backslash before every double quote (being careful with the double
156 # quote we've appended to the end)
158 # quote we've appended to the end)
157 _quotere = None
159 _quotere = None
158 def shellquote(s):
160 def shellquote(s):
159 global _quotere
161 global _quotere
160 if _quotere is None:
162 if _quotere is None:
161 _quotere = re.compile(r'(\\*)("|\\$)')
163 _quotere = re.compile(r'(\\*)("|\\$)')
162 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
164 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
163
165
164 def quotecommand(cmd):
166 def quotecommand(cmd):
165 """Build a command string suitable for os.popen* calls."""
167 """Build a command string suitable for os.popen* calls."""
166 if sys.version_info < (2, 7, 1):
168 if sys.version_info < (2, 7, 1):
167 # Python versions since 2.7.1 do this extra quoting themselves
169 # Python versions since 2.7.1 do this extra quoting themselves
168 return '"' + cmd + '"'
170 return '"' + cmd + '"'
169 return cmd
171 return cmd
170
172
171 def popen(command, mode='r'):
173 def popen(command, mode='r'):
172 # Work around "popen spawned process may not write to stdout
174 # Work around "popen spawned process may not write to stdout
173 # under windows"
175 # under windows"
174 # http://bugs.python.org/issue1366
176 # http://bugs.python.org/issue1366
175 command += " 2> %s" % nulldev
177 command += " 2> %s" % nulldev
176 return os.popen(quotecommand(command), mode)
178 return os.popen(quotecommand(command), mode)
177
179
178 def explainexit(code):
180 def explainexit(code):
179 return _("exited with status %d") % code, code
181 return _("exited with status %d") % code, code
180
182
181 # if you change this stub into a real check, please try to implement the
183 # if you change this stub into a real check, please try to implement the
182 # username and groupname functions above, too.
184 # username and groupname functions above, too.
183 def isowner(st):
185 def isowner(st):
184 return True
186 return True
185
187
186 def findexe(command):
188 def findexe(command):
187 '''Find executable for command searching like cmd.exe does.
189 '''Find executable for command searching like cmd.exe does.
188 If command is a basename then PATH is searched for command.
190 If command is a basename then PATH is searched for command.
189 PATH isn't searched if command is an absolute or relative path.
191 PATH isn't searched if command is an absolute or relative path.
190 An extension from PATHEXT is found and added if not present.
192 An extension from PATHEXT is found and added if not present.
191 If command isn't found None is returned.'''
193 If command isn't found None is returned.'''
192 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
194 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
193 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
195 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
194 if os.path.splitext(command)[1].lower() in pathexts:
196 if os.path.splitext(command)[1].lower() in pathexts:
195 pathexts = ['']
197 pathexts = ['']
196
198
197 def findexisting(pathcommand):
199 def findexisting(pathcommand):
198 'Will append extension (if needed) and return existing file'
200 'Will append extension (if needed) and return existing file'
199 for ext in pathexts:
201 for ext in pathexts:
200 executable = pathcommand + ext
202 executable = pathcommand + ext
201 if os.path.exists(executable):
203 if os.path.exists(executable):
202 return executable
204 return executable
203 return None
205 return None
204
206
205 if os.sep in command:
207 if os.sep in command:
206 return findexisting(command)
208 return findexisting(command)
207
209
208 for path in os.environ.get('PATH', '').split(os.pathsep):
210 for path in os.environ.get('PATH', '').split(os.pathsep):
209 executable = findexisting(os.path.join(path, command))
211 executable = findexisting(os.path.join(path, command))
210 if executable is not None:
212 if executable is not None:
211 return executable
213 return executable
212 return findexisting(os.path.expanduser(os.path.expandvars(command)))
214 return findexisting(os.path.expanduser(os.path.expandvars(command)))
213
215
214 def statfiles(files):
216 def statfiles(files):
215 '''Stat each file in files and yield stat or None if file does not exist.
217 '''Stat each file in files and yield stat or None if file does not exist.
216 Cluster and cache stat per directory to minimize number of OS stat calls.'''
218 Cluster and cache stat per directory to minimize number of OS stat calls.'''
217 ncase = os.path.normcase
219 ncase = os.path.normcase
218 dircache = {} # dirname -> filename -> status | None if file does not exist
220 dircache = {} # dirname -> filename -> status | None if file does not exist
219 for nf in files:
221 for nf in files:
220 nf = ncase(nf)
222 nf = ncase(nf)
221 dir, base = os.path.split(nf)
223 dir, base = os.path.split(nf)
222 if not dir:
224 if not dir:
223 dir = '.'
225 dir = '.'
224 cache = dircache.get(dir, None)
226 cache = dircache.get(dir, None)
225 if cache is None:
227 if cache is None:
226 try:
228 try:
227 dmap = dict([(ncase(n), s)
229 dmap = dict([(ncase(n), s)
228 for n, k, s in osutil.listdir(dir, True)])
230 for n, k, s in osutil.listdir(dir, True)])
229 except OSError, err:
231 except OSError, err:
230 # handle directory not found in Python version prior to 2.5
232 # handle directory not found in Python version prior to 2.5
231 # Python <= 2.4 returns native Windows code 3 in errno
233 # Python <= 2.4 returns native Windows code 3 in errno
232 # Python >= 2.5 returns ENOENT and adds winerror field
234 # Python >= 2.5 returns ENOENT and adds winerror field
233 # EINVAL is raised if dir is not a directory.
235 # EINVAL is raised if dir is not a directory.
234 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
236 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
235 errno.ENOTDIR):
237 errno.ENOTDIR):
236 raise
238 raise
237 dmap = {}
239 dmap = {}
238 cache = dircache.setdefault(dir, dmap)
240 cache = dircache.setdefault(dir, dmap)
239 yield cache.get(base, None)
241 yield cache.get(base, None)
240
242
241 def username(uid=None):
243 def username(uid=None):
242 """Return the name of the user with the given uid.
244 """Return the name of the user with the given uid.
243
245
244 If uid is None, return the name of the current user."""
246 If uid is None, return the name of the current user."""
245 return None
247 return None
246
248
247 def groupname(gid=None):
249 def groupname(gid=None):
248 """Return the name of the group with the given gid.
250 """Return the name of the group with the given gid.
249
251
250 If gid is None, return the name of the current group."""
252 If gid is None, return the name of the current group."""
251 return None
253 return None
252
254
253 def _removedirs(name):
255 def _removedirs(name):
254 """special version of os.removedirs that does not remove symlinked
256 """special version of os.removedirs that does not remove symlinked
255 directories or junction points if they actually contain files"""
257 directories or junction points if they actually contain files"""
256 if osutil.listdir(name):
258 if osutil.listdir(name):
257 return
259 return
258 os.rmdir(name)
260 os.rmdir(name)
259 head, tail = os.path.split(name)
261 head, tail = os.path.split(name)
260 if not tail:
262 if not tail:
261 head, tail = os.path.split(head)
263 head, tail = os.path.split(head)
262 while head and tail:
264 while head and tail:
263 try:
265 try:
264 if osutil.listdir(head):
266 if osutil.listdir(head):
265 return
267 return
266 os.rmdir(head)
268 os.rmdir(head)
267 except (ValueError, OSError):
269 except (ValueError, OSError):
268 break
270 break
269 head, tail = os.path.split(head)
271 head, tail = os.path.split(head)
270
272
271 def unlinkpath(f):
273 def unlinkpath(f):
272 """unlink and remove the directory if it is empty"""
274 """unlink and remove the directory if it is empty"""
273 unlink(f)
275 unlink(f)
274 # try removing directories that might now be empty
276 # try removing directories that might now be empty
275 try:
277 try:
276 _removedirs(os.path.dirname(f))
278 _removedirs(os.path.dirname(f))
277 except OSError:
279 except OSError:
278 pass
280 pass
279
281
280 def rename(src, dst):
282 def rename(src, dst):
281 '''atomically rename file src to dst, replacing dst if it exists'''
283 '''atomically rename file src to dst, replacing dst if it exists'''
282 try:
284 try:
283 os.rename(src, dst)
285 os.rename(src, dst)
284 except OSError, e:
286 except OSError, e:
285 if e.errno != errno.EEXIST:
287 if e.errno != errno.EEXIST:
286 raise
288 raise
287 unlink(dst)
289 unlink(dst)
288 os.rename(src, dst)
290 os.rename(src, dst)
289
291
290 def gethgcmd():
292 def gethgcmd():
291 return [sys.executable] + sys.argv[:1]
293 return [sys.executable] + sys.argv[:1]
292
294
293 def termwidth():
295 def termwidth():
294 # cmd.exe does not handle CR like a unix console, the CR is
296 # cmd.exe does not handle CR like a unix console, the CR is
295 # counted in the line length. On 80 columns consoles, if 80
297 # counted in the line length. On 80 columns consoles, if 80
296 # characters are written, the following CR won't apply on the
298 # characters are written, the following CR won't apply on the
297 # current line but on the new one. Keep room for it.
299 # current line but on the new one. Keep room for it.
298 return 79
300 return 79
299
301
300 def groupmembers(name):
302 def groupmembers(name):
301 # Don't support groups on Windows for now
303 # Don't support groups on Windows for now
302 raise KeyError()
304 raise KeyError()
303
305
304 def isexec(f):
306 def isexec(f):
305 return False
307 return False
306
308
307 class cachestat(object):
309 class cachestat(object):
308 def __init__(self, path):
310 def __init__(self, path):
309 pass
311 pass
310
312
311 def cacheable(self):
313 def cacheable(self):
312 return False
314 return False
313
315
314 expandglobs = True
316 expandglobs = True
@@ -1,193 +1,193 b''
1
1
2 $ . "$TESTDIR/bzr-definitions"
2 $ . "$TESTDIR/bzr-definitions"
3
3
4 empty directory
4 empty directory
5
5
6 $ mkdir test-empty
6 $ mkdir test-empty
7 $ cd test-empty
7 $ cd test-empty
8 $ bzr init -q source
8 $ bzr init -q source
9 $ cd source
9 $ cd source
10 $ echo content > a
10 $ echo content > a
11 $ bzr add -q a
11 $ bzr add -q a
12 $ bzr commit -q -m 'Initial add'
12 $ bzr commit -q -m 'Initial add'
13 $ mkdir empty
13 $ mkdir empty
14 $ bzr add -q empty
14 $ bzr add -q empty
15 $ bzr commit -q -m 'Empty directory added'
15 $ bzr commit -q -m 'Empty directory added'
16 $ echo content > empty/something
16 $ echo content > empty/something
17 $ bzr add -q empty/something
17 $ bzr add -q empty/something
18 $ bzr commit -q -m 'Added file into directory'
18 $ bzr commit -q -m 'Added file into directory'
19 $ cd ..
19 $ cd ..
20 $ hg convert source source-hg
20 $ hg convert source source-hg
21 initializing destination source-hg repository
21 initializing destination source-hg repository
22 scanning source...
22 scanning source...
23 sorting...
23 sorting...
24 converting...
24 converting...
25 2 Initial add
25 2 Initial add
26 1 Empty directory added
26 1 Empty directory added
27 0 Added file into directory
27 0 Added file into directory
28 $ manifest source-hg 1
28 $ manifest source-hg 1
29 % manifest of 1
29 % manifest of 1
30 644 a
30 644 a
31 $ manifest source-hg tip
31 $ manifest source-hg tip
32 % manifest of tip
32 % manifest of tip
33 644 a
33 644 a
34 644 empty/something
34 644 empty/something
35 $ cd ..
35 $ cd ..
36
36
37 directory renames
37 directory renames
38
38
39 $ mkdir test-dir-rename
39 $ mkdir test-dir-rename
40 $ cd test-dir-rename
40 $ cd test-dir-rename
41 $ bzr init -q source
41 $ bzr init -q source
42 $ cd source
42 $ cd source
43 $ mkdir tpyo
43 $ mkdir tpyo
44 $ echo content > tpyo/something
44 $ echo content > tpyo/something
45 $ bzr add -q tpyo
45 $ bzr add -q tpyo
46 $ bzr commit -q -m 'Added directory'
46 $ bzr commit -q -m 'Added directory'
47 $ bzr mv tpyo typo
47 $ bzr mv tpyo typo
48 tpyo => typo
48 tpyo => typo
49 $ bzr commit -q -m 'Oops, typo'
49 $ bzr commit -q -m 'Oops, typo'
50 $ cd ..
50 $ cd ..
51 $ hg convert source source-hg
51 $ hg convert source source-hg
52 initializing destination source-hg repository
52 initializing destination source-hg repository
53 scanning source...
53 scanning source...
54 sorting...
54 sorting...
55 converting...
55 converting...
56 1 Added directory
56 1 Added directory
57 0 Oops, typo
57 0 Oops, typo
58 $ manifest source-hg 0
58 $ manifest source-hg 0
59 % manifest of 0
59 % manifest of 0
60 644 tpyo/something
60 644 tpyo/something
61 $ manifest source-hg tip
61 $ manifest source-hg tip
62 % manifest of tip
62 % manifest of tip
63 644 typo/something
63 644 typo/something
64 $ cd ..
64 $ cd ..
65
65
66 nested directory renames
66 nested directory renames
67
67
68 $ mkdir test-nested-dir-rename
68 $ mkdir test-nested-dir-rename
69 $ cd test-nested-dir-rename
69 $ cd test-nested-dir-rename
70 $ bzr init -q source
70 $ bzr init -q source
71 $ cd source
71 $ cd source
72 $ mkdir -p firstlevel/secondlevel/thirdlevel
72 $ mkdir -p firstlevel/secondlevel/thirdlevel
73 $ echo content > firstlevel/secondlevel/file
73 $ echo content > firstlevel/secondlevel/file
74 $ echo this_needs_to_be_there_too > firstlevel/secondlevel/thirdlevel/stuff
74 $ echo this_needs_to_be_there_too > firstlevel/secondlevel/thirdlevel/stuff
75 $ bzr add -q firstlevel
75 $ bzr add -q firstlevel
76 $ bzr commit -q -m 'Added nested directories'
76 $ bzr commit -q -m 'Added nested directories'
77 $ bzr mv firstlevel/secondlevel secondlevel
77 $ bzr mv firstlevel/secondlevel secondlevel
78 firstlevel/secondlevel => secondlevel
78 firstlevel/secondlevel => secondlevel
79 $ bzr commit -q -m 'Moved secondlevel one level up'
79 $ bzr commit -q -m 'Moved secondlevel one level up'
80 $ cd ..
80 $ cd ..
81 $ hg convert source source-hg
81 $ hg convert source source-hg
82 initializing destination source-hg repository
82 initializing destination source-hg repository
83 scanning source...
83 scanning source...
84 sorting...
84 sorting...
85 converting...
85 converting...
86 1 Added nested directories
86 1 Added nested directories
87 0 Moved secondlevel one level up
87 0 Moved secondlevel one level up
88 $ manifest source-hg tip
88 $ manifest source-hg tip
89 % manifest of tip
89 % manifest of tip
90 644 secondlevel/file
90 644 secondlevel/file
91 644 secondlevel/thirdlevel/stuff
91 644 secondlevel/thirdlevel/stuff
92 $ cd ..
92 $ cd ..
93
93
94 directory remove
94 directory remove
95
95
96 $ mkdir test-dir-remove
96 $ mkdir test-dir-remove
97 $ cd test-dir-remove
97 $ cd test-dir-remove
98 $ bzr init -q source
98 $ bzr init -q source
99 $ cd source
99 $ cd source
100 $ mkdir src
100 $ mkdir src
101 $ echo content > src/sourcecode
101 $ echo content > src/sourcecode
102 $ bzr add -q src
102 $ bzr add -q src
103 $ bzr commit -q -m 'Added directory'
103 $ bzr commit -q -m 'Added directory'
104 $ bzr rm -q src
104 $ bzr rm -q src
105 $ bzr commit -q -m 'Removed directory'
105 $ bzr commit -q -m 'Removed directory'
106 $ cd ..
106 $ cd ..
107 $ hg convert source source-hg
107 $ hg convert source source-hg
108 initializing destination source-hg repository
108 initializing destination source-hg repository
109 scanning source...
109 scanning source...
110 sorting...
110 sorting...
111 converting...
111 converting...
112 1 Added directory
112 1 Added directory
113 0 Removed directory
113 0 Removed directory
114 $ manifest source-hg 0
114 $ manifest source-hg 0
115 % manifest of 0
115 % manifest of 0
116 644 src/sourcecode
116 644 src/sourcecode
117 $ manifest source-hg tip
117 $ manifest source-hg tip
118 % manifest of tip
118 % manifest of tip
119 $ cd ..
119 $ cd ..
120
120
121 directory replace
121 directory replace
122
122
123 $ mkdir test-dir-replace
123 $ mkdir test-dir-replace
124 $ cd test-dir-replace
124 $ cd test-dir-replace
125 $ bzr init -q source
125 $ bzr init -q source
126 $ cd source
126 $ cd source
127 $ mkdir first second
127 $ mkdir first second
128 $ echo content > first/file
128 $ echo content > first/file
129 $ echo morecontent > first/dummy
129 $ echo morecontent > first/dummy
130 $ echo othercontent > second/something
130 $ echo othercontent > second/something
131 $ bzr add -q first second
131 $ bzr add -q first second
132 $ bzr commit -q -m 'Initial layout'
132 $ bzr commit -q -m 'Initial layout'
133 $ bzr mv first/file second/file
133 $ bzr mv first/file second/file
134 first/file => second/file
134 first/file => second/file
135 $ bzr mv first third
135 $ bzr mv first third
136 first => third
136 first => third
137 $ bzr commit -q -m 'Some conflicting moves'
137 $ bzr commit -q -m 'Some conflicting moves'
138 $ cd ..
138 $ cd ..
139 $ hg convert source source-hg
139 $ hg convert source source-hg
140 initializing destination source-hg repository
140 initializing destination source-hg repository
141 scanning source...
141 scanning source...
142 sorting...
142 sorting...
143 converting...
143 converting...
144 1 Initial layout
144 1 Initial layout
145 0 Some conflicting moves
145 0 Some conflicting moves
146 $ manifest source-hg tip
146 $ manifest source-hg tip
147 % manifest of tip
147 % manifest of tip
148 644 second/file
148 644 second/file
149 644 second/something
149 644 second/something
150 644 third/dummy
150 644 third/dummy
151 $ cd ..
151 $ cd ..
152
152
153 divergent nested renames (issue3089)
153 divergent nested renames (issue3089)
154
154
155 $ mkdir test-divergent-renames
155 $ mkdir test-divergent-renames
156 $ cd test-divergent-renames
156 $ cd test-divergent-renames
157 $ bzr init -q source
157 $ bzr init -q source
158 $ cd source
158 $ cd source
159 $ mkdir -p a/c
159 $ mkdir -p a/c
160 $ echo a > a/fa
160 $ echo a > a/fa
161 $ echo c > a/c/fc
161 $ echo c > a/c/fc
162 $ bzr add -q a
162 $ bzr add -q a
163 $ bzr commit -q -m 'Initial layout'
163 $ bzr commit -q -m 'Initial layout'
164 $ bzr mv a b
164 $ bzr mv a b
165 a => b
165 a => b
166 $ mkdir a
166 $ mkdir a
167 $ bzr add a
167 $ bzr add a
168 adding a
168 add(ed|ing) a (re)
169 $ bzr mv b/c a/c
169 $ bzr mv b/c a/c
170 b/c => a/c
170 b/c => a/c
171 $ bzr status
171 $ bzr status
172 added:
172 added:
173 a/
173 a/
174 renamed:
174 renamed:
175 a/ => b/
175 a/? => b/? (re)
176 a/c/ => a/c/
176 a/c/? => a/c/? (re)
177 $ bzr commit -q -m 'Divergent renames'
177 $ bzr commit -q -m 'Divergent renames'
178 $ cd ..
178 $ cd ..
179 $ hg convert source source-hg
179 $ hg convert source source-hg
180 initializing destination source-hg repository
180 initializing destination source-hg repository
181 scanning source...
181 scanning source...
182 sorting...
182 sorting...
183 converting...
183 converting...
184 1 Initial layout
184 1 Initial layout
185 0 Divergent renames
185 0 Divergent renames
186 $ hg -R source-hg st -C --change 1
186 $ hg -R source-hg st -C --change 1
187 A b/fa
187 A b/fa
188 a/fa
188 a/fa
189 R a/fa
189 R a/fa
190 $ hg -R source-hg manifest -r 1
190 $ hg -R source-hg manifest -r 1
191 a/c/fc
191 a/c/fc
192 b/fa
192 b/fa
193 $ cd ..
193 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now