##// END OF EJS Templates
util._matcher: use "." as the root of empty {rel,}path patterns...
Alexis S. L. Carvalho -
r4233:03a665f9 default
parent child Browse files
Show More
@@ -1,1383 +1,1383
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import gettext as _
15 from i18n import gettext as _
16 from demandload import *
16 from demandload import *
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
19
19
20 try:
20 try:
21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
22 or "ascii"
22 or "ascii"
23 except locale.Error:
23 except locale.Error:
24 _encoding = 'ascii'
24 _encoding = 'ascii'
25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
26 _fallbackencoding = 'ISO-8859-1'
26 _fallbackencoding = 'ISO-8859-1'
27
27
28 def tolocal(s):
28 def tolocal(s):
29 """
29 """
30 Convert a string from internal UTF-8 to local encoding
30 Convert a string from internal UTF-8 to local encoding
31
31
32 All internal strings should be UTF-8 but some repos before the
32 All internal strings should be UTF-8 but some repos before the
33 implementation of locale support may contain latin1 or possibly
33 implementation of locale support may contain latin1 or possibly
34 other character sets. We attempt to decode everything strictly
34 other character sets. We attempt to decode everything strictly
35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
36 replace unknown characters.
36 replace unknown characters.
37 """
37 """
38 for e in ('UTF-8', _fallbackencoding):
38 for e in ('UTF-8', _fallbackencoding):
39 try:
39 try:
40 u = s.decode(e) # attempt strict decoding
40 u = s.decode(e) # attempt strict decoding
41 return u.encode(_encoding, "replace")
41 return u.encode(_encoding, "replace")
42 except LookupError, k:
42 except LookupError, k:
43 raise Abort(_("%s, please check your locale settings") % k)
43 raise Abort(_("%s, please check your locale settings") % k)
44 except UnicodeDecodeError:
44 except UnicodeDecodeError:
45 pass
45 pass
46 u = s.decode("utf-8", "replace") # last ditch
46 u = s.decode("utf-8", "replace") # last ditch
47 return u.encode(_encoding, "replace")
47 return u.encode(_encoding, "replace")
48
48
49 def fromlocal(s):
49 def fromlocal(s):
50 """
50 """
51 Convert a string from the local character encoding to UTF-8
51 Convert a string from the local character encoding to UTF-8
52
52
53 We attempt to decode strings using the encoding mode set by
53 We attempt to decode strings using the encoding mode set by
54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
55 characters will cause an error message. Other modes include
55 characters will cause an error message. Other modes include
56 'replace', which replaces unknown characters with a special
56 'replace', which replaces unknown characters with a special
57 Unicode character, and 'ignore', which drops the character.
57 Unicode character, and 'ignore', which drops the character.
58 """
58 """
59 try:
59 try:
60 return s.decode(_encoding, _encodingmode).encode("utf-8")
60 return s.decode(_encoding, _encodingmode).encode("utf-8")
61 except UnicodeDecodeError, inst:
61 except UnicodeDecodeError, inst:
62 sub = s[max(0, inst.start-10):inst.start+10]
62 sub = s[max(0, inst.start-10):inst.start+10]
63 raise Abort("decoding near '%s': %s!" % (sub, inst))
63 raise Abort("decoding near '%s': %s!" % (sub, inst))
64 except LookupError, k:
64 except LookupError, k:
65 raise Abort(_("%s, please check your locale settings") % k)
65 raise Abort(_("%s, please check your locale settings") % k)
66
66
67 def locallen(s):
67 def locallen(s):
68 """Find the length in characters of a local string"""
68 """Find the length in characters of a local string"""
69 return len(s.decode(_encoding, "replace"))
69 return len(s.decode(_encoding, "replace"))
70
70
71 def localsub(s, a, b=None):
71 def localsub(s, a, b=None):
72 try:
72 try:
73 u = s.decode(_encoding, _encodingmode)
73 u = s.decode(_encoding, _encodingmode)
74 if b is not None:
74 if b is not None:
75 u = u[a:b]
75 u = u[a:b]
76 else:
76 else:
77 u = u[:a]
77 u = u[:a]
78 return u.encode(_encoding, _encodingmode)
78 return u.encode(_encoding, _encodingmode)
79 except UnicodeDecodeError, inst:
79 except UnicodeDecodeError, inst:
80 sub = s[max(0, inst.start-10), inst.start+10]
80 sub = s[max(0, inst.start-10), inst.start+10]
81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%b %d %H:%M:%S %Y',
96 '%b %d %H:%M:%S %Y',
97 '%b %d %I:%M:%S%p %Y',
97 '%b %d %I:%M:%S%p %Y',
98 '%b %d %H:%M:%S',
98 '%b %d %H:%M:%S',
99 '%b %d %I:%M:%S%p',
99 '%b %d %I:%M:%S%p',
100 '%b %d %H:%M',
100 '%b %d %H:%M',
101 '%b %d %I:%M%p',
101 '%b %d %I:%M%p',
102 '%b %d %Y',
102 '%b %d %Y',
103 '%b %d',
103 '%b %d',
104 '%H:%M:%S',
104 '%H:%M:%S',
105 '%I:%M:%SP',
105 '%I:%M:%SP',
106 '%H:%M',
106 '%H:%M',
107 '%I:%M%p',
107 '%I:%M%p',
108 )
108 )
109
109
110 extendeddateformats = defaultdateformats + (
110 extendeddateformats = defaultdateformats + (
111 "%Y",
111 "%Y",
112 "%Y-%m",
112 "%Y-%m",
113 "%b",
113 "%b",
114 "%b %Y",
114 "%b %Y",
115 )
115 )
116
116
117 class SignalInterrupt(Exception):
117 class SignalInterrupt(Exception):
118 """Exception raised on SIGTERM and SIGHUP."""
118 """Exception raised on SIGTERM and SIGHUP."""
119
119
120 # like SafeConfigParser but with case-sensitive keys
120 # like SafeConfigParser but with case-sensitive keys
121 class configparser(ConfigParser.SafeConfigParser):
121 class configparser(ConfigParser.SafeConfigParser):
122 def optionxform(self, optionstr):
122 def optionxform(self, optionstr):
123 return optionstr
123 return optionstr
124
124
125 def cachefunc(func):
125 def cachefunc(func):
126 '''cache the result of function calls'''
126 '''cache the result of function calls'''
127 # XXX doesn't handle keywords args
127 # XXX doesn't handle keywords args
128 cache = {}
128 cache = {}
129 if func.func_code.co_argcount == 1:
129 if func.func_code.co_argcount == 1:
130 # we gain a small amount of time because
130 # we gain a small amount of time because
131 # we don't need to pack/unpack the list
131 # we don't need to pack/unpack the list
132 def f(arg):
132 def f(arg):
133 if arg not in cache:
133 if arg not in cache:
134 cache[arg] = func(arg)
134 cache[arg] = func(arg)
135 return cache[arg]
135 return cache[arg]
136 else:
136 else:
137 def f(*args):
137 def f(*args):
138 if args not in cache:
138 if args not in cache:
139 cache[args] = func(*args)
139 cache[args] = func(*args)
140 return cache[args]
140 return cache[args]
141
141
142 return f
142 return f
143
143
144 def pipefilter(s, cmd):
144 def pipefilter(s, cmd):
145 '''filter string S through command CMD, returning its output'''
145 '''filter string S through command CMD, returning its output'''
146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
147 def writer():
147 def writer():
148 try:
148 try:
149 pin.write(s)
149 pin.write(s)
150 pin.close()
150 pin.close()
151 except IOError, inst:
151 except IOError, inst:
152 if inst.errno != errno.EPIPE:
152 if inst.errno != errno.EPIPE:
153 raise
153 raise
154
154
155 # we should use select instead on UNIX, but this will work on most
155 # we should use select instead on UNIX, but this will work on most
156 # systems, including Windows
156 # systems, including Windows
157 w = threading.Thread(target=writer)
157 w = threading.Thread(target=writer)
158 w.start()
158 w.start()
159 f = pout.read()
159 f = pout.read()
160 pout.close()
160 pout.close()
161 w.join()
161 w.join()
162 return f
162 return f
163
163
164 def tempfilter(s, cmd):
164 def tempfilter(s, cmd):
165 '''filter string S through a pair of temporary files with CMD.
165 '''filter string S through a pair of temporary files with CMD.
166 CMD is used as a template to create the real command to be run,
166 CMD is used as a template to create the real command to be run,
167 with the strings INFILE and OUTFILE replaced by the real names of
167 with the strings INFILE and OUTFILE replaced by the real names of
168 the temporary files generated.'''
168 the temporary files generated.'''
169 inname, outname = None, None
169 inname, outname = None, None
170 try:
170 try:
171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
172 fp = os.fdopen(infd, 'wb')
172 fp = os.fdopen(infd, 'wb')
173 fp.write(s)
173 fp.write(s)
174 fp.close()
174 fp.close()
175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
176 os.close(outfd)
176 os.close(outfd)
177 cmd = cmd.replace('INFILE', inname)
177 cmd = cmd.replace('INFILE', inname)
178 cmd = cmd.replace('OUTFILE', outname)
178 cmd = cmd.replace('OUTFILE', outname)
179 code = os.system(cmd)
179 code = os.system(cmd)
180 if code: raise Abort(_("command '%s' failed: %s") %
180 if code: raise Abort(_("command '%s' failed: %s") %
181 (cmd, explain_exit(code)))
181 (cmd, explain_exit(code)))
182 return open(outname, 'rb').read()
182 return open(outname, 'rb').read()
183 finally:
183 finally:
184 try:
184 try:
185 if inname: os.unlink(inname)
185 if inname: os.unlink(inname)
186 except: pass
186 except: pass
187 try:
187 try:
188 if outname: os.unlink(outname)
188 if outname: os.unlink(outname)
189 except: pass
189 except: pass
190
190
191 filtertable = {
191 filtertable = {
192 'tempfile:': tempfilter,
192 'tempfile:': tempfilter,
193 'pipe:': pipefilter,
193 'pipe:': pipefilter,
194 }
194 }
195
195
196 def filter(s, cmd):
196 def filter(s, cmd):
197 "filter a string through a command that transforms its input to its output"
197 "filter a string through a command that transforms its input to its output"
198 for name, fn in filtertable.iteritems():
198 for name, fn in filtertable.iteritems():
199 if cmd.startswith(name):
199 if cmd.startswith(name):
200 return fn(s, cmd[len(name):].lstrip())
200 return fn(s, cmd[len(name):].lstrip())
201 return pipefilter(s, cmd)
201 return pipefilter(s, cmd)
202
202
203 def find_in_path(name, path, default=None):
203 def find_in_path(name, path, default=None):
204 '''find name in search path. path can be string (will be split
204 '''find name in search path. path can be string (will be split
205 with os.pathsep), or iterable thing that returns strings. if name
205 with os.pathsep), or iterable thing that returns strings. if name
206 found, return path to name. else return default.'''
206 found, return path to name. else return default.'''
207 if isinstance(path, str):
207 if isinstance(path, str):
208 path = path.split(os.pathsep)
208 path = path.split(os.pathsep)
209 for p in path:
209 for p in path:
210 p_name = os.path.join(p, name)
210 p_name = os.path.join(p, name)
211 if os.path.exists(p_name):
211 if os.path.exists(p_name):
212 return p_name
212 return p_name
213 return default
213 return default
214
214
215 def binary(s):
215 def binary(s):
216 """return true if a string is binary data using diff's heuristic"""
216 """return true if a string is binary data using diff's heuristic"""
217 if s and '\0' in s[:4096]:
217 if s and '\0' in s[:4096]:
218 return True
218 return True
219 return False
219 return False
220
220
221 def unique(g):
221 def unique(g):
222 """return the uniq elements of iterable g"""
222 """return the uniq elements of iterable g"""
223 seen = {}
223 seen = {}
224 l = []
224 l = []
225 for f in g:
225 for f in g:
226 if f not in seen:
226 if f not in seen:
227 seen[f] = 1
227 seen[f] = 1
228 l.append(f)
228 l.append(f)
229 return l
229 return l
230
230
231 class Abort(Exception):
231 class Abort(Exception):
232 """Raised if a command needs to print an error and exit."""
232 """Raised if a command needs to print an error and exit."""
233
233
234 class UnexpectedOutput(Abort):
234 class UnexpectedOutput(Abort):
235 """Raised to print an error with part of output and exit."""
235 """Raised to print an error with part of output and exit."""
236
236
237 def always(fn): return True
237 def always(fn): return True
238 def never(fn): return False
238 def never(fn): return False
239
239
240 def expand_glob(pats):
240 def expand_glob(pats):
241 '''On Windows, expand the implicit globs in a list of patterns'''
241 '''On Windows, expand the implicit globs in a list of patterns'''
242 if os.name != 'nt':
242 if os.name != 'nt':
243 return list(pats)
243 return list(pats)
244 ret = []
244 ret = []
245 for p in pats:
245 for p in pats:
246 kind, name = patkind(p, None)
246 kind, name = patkind(p, None)
247 if kind is None:
247 if kind is None:
248 globbed = glob.glob(name)
248 globbed = glob.glob(name)
249 if globbed:
249 if globbed:
250 ret.extend(globbed)
250 ret.extend(globbed)
251 continue
251 continue
252 # if we couldn't expand the glob, just keep it around
252 # if we couldn't expand the glob, just keep it around
253 ret.append(p)
253 ret.append(p)
254 return ret
254 return ret
255
255
256 def patkind(name, dflt_pat='glob'):
256 def patkind(name, dflt_pat='glob'):
257 """Split a string into an optional pattern kind prefix and the
257 """Split a string into an optional pattern kind prefix and the
258 actual pattern."""
258 actual pattern."""
259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
260 if name.startswith(prefix + ':'): return name.split(':', 1)
260 if name.startswith(prefix + ':'): return name.split(':', 1)
261 return dflt_pat, name
261 return dflt_pat, name
262
262
263 def globre(pat, head='^', tail='$'):
263 def globre(pat, head='^', tail='$'):
264 "convert a glob pattern into a regexp"
264 "convert a glob pattern into a regexp"
265 i, n = 0, len(pat)
265 i, n = 0, len(pat)
266 res = ''
266 res = ''
267 group = False
267 group = False
268 def peek(): return i < n and pat[i]
268 def peek(): return i < n and pat[i]
269 while i < n:
269 while i < n:
270 c = pat[i]
270 c = pat[i]
271 i = i+1
271 i = i+1
272 if c == '*':
272 if c == '*':
273 if peek() == '*':
273 if peek() == '*':
274 i += 1
274 i += 1
275 res += '.*'
275 res += '.*'
276 else:
276 else:
277 res += '[^/]*'
277 res += '[^/]*'
278 elif c == '?':
278 elif c == '?':
279 res += '.'
279 res += '.'
280 elif c == '[':
280 elif c == '[':
281 j = i
281 j = i
282 if j < n and pat[j] in '!]':
282 if j < n and pat[j] in '!]':
283 j += 1
283 j += 1
284 while j < n and pat[j] != ']':
284 while j < n and pat[j] != ']':
285 j += 1
285 j += 1
286 if j >= n:
286 if j >= n:
287 res += '\\['
287 res += '\\['
288 else:
288 else:
289 stuff = pat[i:j].replace('\\','\\\\')
289 stuff = pat[i:j].replace('\\','\\\\')
290 i = j + 1
290 i = j + 1
291 if stuff[0] == '!':
291 if stuff[0] == '!':
292 stuff = '^' + stuff[1:]
292 stuff = '^' + stuff[1:]
293 elif stuff[0] == '^':
293 elif stuff[0] == '^':
294 stuff = '\\' + stuff
294 stuff = '\\' + stuff
295 res = '%s[%s]' % (res, stuff)
295 res = '%s[%s]' % (res, stuff)
296 elif c == '{':
296 elif c == '{':
297 group = True
297 group = True
298 res += '(?:'
298 res += '(?:'
299 elif c == '}' and group:
299 elif c == '}' and group:
300 res += ')'
300 res += ')'
301 group = False
301 group = False
302 elif c == ',' and group:
302 elif c == ',' and group:
303 res += '|'
303 res += '|'
304 elif c == '\\':
304 elif c == '\\':
305 p = peek()
305 p = peek()
306 if p:
306 if p:
307 i += 1
307 i += 1
308 res += re.escape(p)
308 res += re.escape(p)
309 else:
309 else:
310 res += re.escape(c)
310 res += re.escape(c)
311 else:
311 else:
312 res += re.escape(c)
312 res += re.escape(c)
313 return head + res + tail
313 return head + res + tail
314
314
315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
316
316
317 def pathto(n1, n2):
317 def pathto(n1, n2):
318 '''return the relative path from one place to another.
318 '''return the relative path from one place to another.
319 n1 should use os.sep to separate directories
319 n1 should use os.sep to separate directories
320 n2 should use "/" to separate directories
320 n2 should use "/" to separate directories
321 returns an os.sep-separated path.
321 returns an os.sep-separated path.
322 '''
322 '''
323 if not n1: return localpath(n2)
323 if not n1: return localpath(n2)
324 a, b = n1.split(os.sep), n2.split('/')
324 a, b = n1.split(os.sep), n2.split('/')
325 a.reverse()
325 a.reverse()
326 b.reverse()
326 b.reverse()
327 while a and b and a[-1] == b[-1]:
327 while a and b and a[-1] == b[-1]:
328 a.pop()
328 a.pop()
329 b.pop()
329 b.pop()
330 b.reverse()
330 b.reverse()
331 return os.sep.join((['..'] * len(a)) + b)
331 return os.sep.join((['..'] * len(a)) + b)
332
332
333 def canonpath(root, cwd, myname):
333 def canonpath(root, cwd, myname):
334 """return the canonical path of myname, given cwd and root"""
334 """return the canonical path of myname, given cwd and root"""
335 if root == os.sep:
335 if root == os.sep:
336 rootsep = os.sep
336 rootsep = os.sep
337 elif root.endswith(os.sep):
337 elif root.endswith(os.sep):
338 rootsep = root
338 rootsep = root
339 else:
339 else:
340 rootsep = root + os.sep
340 rootsep = root + os.sep
341 name = myname
341 name = myname
342 if not os.path.isabs(name):
342 if not os.path.isabs(name):
343 name = os.path.join(root, cwd, name)
343 name = os.path.join(root, cwd, name)
344 name = os.path.normpath(name)
344 name = os.path.normpath(name)
345 if name != rootsep and name.startswith(rootsep):
345 if name != rootsep and name.startswith(rootsep):
346 name = name[len(rootsep):]
346 name = name[len(rootsep):]
347 audit_path(name)
347 audit_path(name)
348 return pconvert(name)
348 return pconvert(name)
349 elif name == root:
349 elif name == root:
350 return ''
350 return ''
351 else:
351 else:
352 # Determine whether `name' is in the hierarchy at or beneath `root',
352 # Determine whether `name' is in the hierarchy at or beneath `root',
353 # by iterating name=dirname(name) until that causes no change (can't
353 # by iterating name=dirname(name) until that causes no change (can't
354 # check name == '/', because that doesn't work on windows). For each
354 # check name == '/', because that doesn't work on windows). For each
355 # `name', compare dev/inode numbers. If they match, the list `rel'
355 # `name', compare dev/inode numbers. If they match, the list `rel'
356 # holds the reversed list of components making up the relative file
356 # holds the reversed list of components making up the relative file
357 # name we want.
357 # name we want.
358 root_st = os.stat(root)
358 root_st = os.stat(root)
359 rel = []
359 rel = []
360 while True:
360 while True:
361 try:
361 try:
362 name_st = os.stat(name)
362 name_st = os.stat(name)
363 except OSError:
363 except OSError:
364 break
364 break
365 if samestat(name_st, root_st):
365 if samestat(name_st, root_st):
366 if not rel:
366 if not rel:
367 # name was actually the same as root (maybe a symlink)
367 # name was actually the same as root (maybe a symlink)
368 return ''
368 return ''
369 rel.reverse()
369 rel.reverse()
370 name = os.path.join(*rel)
370 name = os.path.join(*rel)
371 audit_path(name)
371 audit_path(name)
372 return pconvert(name)
372 return pconvert(name)
373 dirname, basename = os.path.split(name)
373 dirname, basename = os.path.split(name)
374 rel.append(basename)
374 rel.append(basename)
375 if dirname == name:
375 if dirname == name:
376 break
376 break
377 name = dirname
377 name = dirname
378
378
379 raise Abort('%s not under root' % myname)
379 raise Abort('%s not under root' % myname)
380
380
381 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
381 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
382 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
382 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
383
383
384 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
384 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
385 globbed=False, default=None):
385 globbed=False, default=None):
386 default = default or 'relpath'
386 default = default or 'relpath'
387 if default == 'relpath' and not globbed:
387 if default == 'relpath' and not globbed:
388 names = expand_glob(names)
388 names = expand_glob(names)
389 return _matcher(canonroot, cwd, names, inc, exc, default, src)
389 return _matcher(canonroot, cwd, names, inc, exc, default, src)
390
390
391 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
391 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
392 """build a function to match a set of file patterns
392 """build a function to match a set of file patterns
393
393
394 arguments:
394 arguments:
395 canonroot - the canonical root of the tree you're matching against
395 canonroot - the canonical root of the tree you're matching against
396 cwd - the current working directory, if relevant
396 cwd - the current working directory, if relevant
397 names - patterns to find
397 names - patterns to find
398 inc - patterns to include
398 inc - patterns to include
399 exc - patterns to exclude
399 exc - patterns to exclude
400 dflt_pat - if a pattern in names has no explicit type, assume this one
400 dflt_pat - if a pattern in names has no explicit type, assume this one
401 src - where these patterns came from (e.g. .hgignore)
401 src - where these patterns came from (e.g. .hgignore)
402
402
403 a pattern is one of:
403 a pattern is one of:
404 'glob:<glob>' - a glob relative to cwd
404 'glob:<glob>' - a glob relative to cwd
405 're:<regexp>' - a regular expression
405 're:<regexp>' - a regular expression
406 'path:<path>' - a path relative to canonroot
406 'path:<path>' - a path relative to canonroot
407 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
407 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
408 'relpath:<path>' - a path relative to cwd
408 'relpath:<path>' - a path relative to cwd
409 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
409 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
410 '<something>' - one of the cases above, selected by the dflt_pat argument
410 '<something>' - one of the cases above, selected by the dflt_pat argument
411
411
412 returns:
412 returns:
413 a 3-tuple containing
413 a 3-tuple containing
414 - list of roots (places where one should start a recursive walk of the fs);
414 - list of roots (places where one should start a recursive walk of the fs);
415 this often matches the explicit non-pattern names passed in, but also
415 this often matches the explicit non-pattern names passed in, but also
416 includes the initial part of glob: patterns that has no glob characters
416 includes the initial part of glob: patterns that has no glob characters
417 - a bool match(filename) function
417 - a bool match(filename) function
418 - a bool indicating if any patterns were passed in
418 - a bool indicating if any patterns were passed in
419 """
419 """
420
420
421 # a common case: no patterns at all
421 # a common case: no patterns at all
422 if not names and not inc and not exc:
422 if not names and not inc and not exc:
423 return [], always, False
423 return [], always, False
424
424
425 def contains_glob(name):
425 def contains_glob(name):
426 for c in name:
426 for c in name:
427 if c in _globchars: return True
427 if c in _globchars: return True
428 return False
428 return False
429
429
430 def regex(kind, name, tail):
430 def regex(kind, name, tail):
431 '''convert a pattern into a regular expression'''
431 '''convert a pattern into a regular expression'''
432 if not name:
432 if not name:
433 return ''
433 return ''
434 if kind == 're':
434 if kind == 're':
435 return name
435 return name
436 elif kind == 'path':
436 elif kind == 'path':
437 return '^' + re.escape(name) + '(?:/|$)'
437 return '^' + re.escape(name) + '(?:/|$)'
438 elif kind == 'relglob':
438 elif kind == 'relglob':
439 return globre(name, '(?:|.*/)', '(?:/|$)')
439 return globre(name, '(?:|.*/)', '(?:/|$)')
440 elif kind == 'relpath':
440 elif kind == 'relpath':
441 return re.escape(name) + '(?:/|$)'
441 return re.escape(name) + '(?:/|$)'
442 elif kind == 'relre':
442 elif kind == 'relre':
443 if name.startswith('^'):
443 if name.startswith('^'):
444 return name
444 return name
445 return '.*' + name
445 return '.*' + name
446 return globre(name, '', tail)
446 return globre(name, '', tail)
447
447
448 def matchfn(pats, tail):
448 def matchfn(pats, tail):
449 """build a matching function from a set of patterns"""
449 """build a matching function from a set of patterns"""
450 if not pats:
450 if not pats:
451 return
451 return
452 matches = []
452 matches = []
453 for k, p in pats:
453 for k, p in pats:
454 try:
454 try:
455 pat = '(?:%s)' % regex(k, p, tail)
455 pat = '(?:%s)' % regex(k, p, tail)
456 matches.append(re.compile(pat).match)
456 matches.append(re.compile(pat).match)
457 except re.error:
457 except re.error:
458 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
458 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
459 else: raise Abort("invalid pattern (%s): %s" % (k, p))
459 else: raise Abort("invalid pattern (%s): %s" % (k, p))
460
460
461 def buildfn(text):
461 def buildfn(text):
462 for m in matches:
462 for m in matches:
463 r = m(text)
463 r = m(text)
464 if r:
464 if r:
465 return r
465 return r
466
466
467 return buildfn
467 return buildfn
468
468
469 def globprefix(pat):
469 def globprefix(pat):
470 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
470 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
471 root = []
471 root = []
472 for p in pat.split('/'):
472 for p in pat.split('/'):
473 if contains_glob(p): break
473 if contains_glob(p): break
474 root.append(p)
474 root.append(p)
475 return '/'.join(root) or '.'
475 return '/'.join(root) or '.'
476
476
477 def normalizepats(names, default):
477 def normalizepats(names, default):
478 pats = []
478 pats = []
479 files = []
479 files = []
480 roots = []
480 roots = []
481 anypats = False
481 anypats = False
482 for kind, name in [patkind(p, default) for p in names]:
482 for kind, name in [patkind(p, default) for p in names]:
483 if kind in ('glob', 'relpath'):
483 if kind in ('glob', 'relpath'):
484 name = canonpath(canonroot, cwd, name)
484 name = canonpath(canonroot, cwd, name)
485 elif kind in ('relglob', 'path'):
485 elif kind in ('relglob', 'path'):
486 name = normpath(name)
486 name = normpath(name)
487 if kind in ('glob', 're', 'relglob', 'relre'):
487 if kind in ('glob', 're', 'relglob', 'relre'):
488 pats.append((kind, name))
488 pats.append((kind, name))
489 anypats = True
489 anypats = True
490 if kind == 'glob':
490 if kind == 'glob':
491 root = globprefix(name)
491 root = globprefix(name)
492 roots.append(root)
492 roots.append(root)
493 elif kind in ('relpath', 'path'):
493 elif kind in ('relpath', 'path'):
494 files.append((kind, name))
494 files.append((kind, name))
495 roots.append(name)
495 roots.append(name or '.')
496 elif kind == 'relglob':
496 elif kind == 'relglob':
497 roots.append('.')
497 roots.append('.')
498 return roots, pats + files, anypats
498 return roots, pats + files, anypats
499
499
500 roots, pats, anypats = normalizepats(names, dflt_pat)
500 roots, pats, anypats = normalizepats(names, dflt_pat)
501
501
502 patmatch = matchfn(pats, '$') or always
502 patmatch = matchfn(pats, '$') or always
503 incmatch = always
503 incmatch = always
504 if inc:
504 if inc:
505 dummy, inckinds, dummy = normalizepats(inc, 'glob')
505 dummy, inckinds, dummy = normalizepats(inc, 'glob')
506 incmatch = matchfn(inckinds, '(?:/|$)')
506 incmatch = matchfn(inckinds, '(?:/|$)')
507 excmatch = lambda fn: False
507 excmatch = lambda fn: False
508 if exc:
508 if exc:
509 dummy, exckinds, dummy = normalizepats(exc, 'glob')
509 dummy, exckinds, dummy = normalizepats(exc, 'glob')
510 excmatch = matchfn(exckinds, '(?:/|$)')
510 excmatch = matchfn(exckinds, '(?:/|$)')
511
511
512 if not names and inc and not exc:
512 if not names and inc and not exc:
513 # common case: hgignore patterns
513 # common case: hgignore patterns
514 match = incmatch
514 match = incmatch
515 else:
515 else:
516 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
516 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
517
517
518 return (roots, match, (inc or exc or anypats) and True)
518 return (roots, match, (inc or exc or anypats) and True)
519
519
520 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
520 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
521 '''enhanced shell command execution.
521 '''enhanced shell command execution.
522 run with environment maybe modified, maybe in different dir.
522 run with environment maybe modified, maybe in different dir.
523
523
524 if command fails and onerr is None, return status. if ui object,
524 if command fails and onerr is None, return status. if ui object,
525 print error message and return status, else raise onerr object as
525 print error message and return status, else raise onerr object as
526 exception.'''
526 exception.'''
527 def py2shell(val):
527 def py2shell(val):
528 'convert python object into string that is useful to shell'
528 'convert python object into string that is useful to shell'
529 if val in (None, False):
529 if val in (None, False):
530 return '0'
530 return '0'
531 if val == True:
531 if val == True:
532 return '1'
532 return '1'
533 return str(val)
533 return str(val)
534 oldenv = {}
534 oldenv = {}
535 for k in environ:
535 for k in environ:
536 oldenv[k] = os.environ.get(k)
536 oldenv[k] = os.environ.get(k)
537 if cwd is not None:
537 if cwd is not None:
538 oldcwd = os.getcwd()
538 oldcwd = os.getcwd()
539 origcmd = cmd
539 origcmd = cmd
540 if os.name == 'nt':
540 if os.name == 'nt':
541 cmd = '"%s"' % cmd
541 cmd = '"%s"' % cmd
542 try:
542 try:
543 for k, v in environ.iteritems():
543 for k, v in environ.iteritems():
544 os.environ[k] = py2shell(v)
544 os.environ[k] = py2shell(v)
545 if cwd is not None and oldcwd != cwd:
545 if cwd is not None and oldcwd != cwd:
546 os.chdir(cwd)
546 os.chdir(cwd)
547 rc = os.system(cmd)
547 rc = os.system(cmd)
548 if rc and onerr:
548 if rc and onerr:
549 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
549 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
550 explain_exit(rc)[0])
550 explain_exit(rc)[0])
551 if errprefix:
551 if errprefix:
552 errmsg = '%s: %s' % (errprefix, errmsg)
552 errmsg = '%s: %s' % (errprefix, errmsg)
553 try:
553 try:
554 onerr.warn(errmsg + '\n')
554 onerr.warn(errmsg + '\n')
555 except AttributeError:
555 except AttributeError:
556 raise onerr(errmsg)
556 raise onerr(errmsg)
557 return rc
557 return rc
558 finally:
558 finally:
559 for k, v in oldenv.iteritems():
559 for k, v in oldenv.iteritems():
560 if v is None:
560 if v is None:
561 del os.environ[k]
561 del os.environ[k]
562 else:
562 else:
563 os.environ[k] = v
563 os.environ[k] = v
564 if cwd is not None and oldcwd != cwd:
564 if cwd is not None and oldcwd != cwd:
565 os.chdir(oldcwd)
565 os.chdir(oldcwd)
566
566
567 def rename(src, dst):
567 def rename(src, dst):
568 """forcibly rename a file"""
568 """forcibly rename a file"""
569 try:
569 try:
570 os.rename(src, dst)
570 os.rename(src, dst)
571 except OSError, err:
571 except OSError, err:
572 # on windows, rename to existing file is not allowed, so we
572 # on windows, rename to existing file is not allowed, so we
573 # must delete destination first. but if file is open, unlink
573 # must delete destination first. but if file is open, unlink
574 # schedules it for delete but does not delete it. rename
574 # schedules it for delete but does not delete it. rename
575 # happens immediately even for open files, so we create
575 # happens immediately even for open files, so we create
576 # temporary file, delete it, rename destination to that name,
576 # temporary file, delete it, rename destination to that name,
577 # then delete that. then rename is safe to do.
577 # then delete that. then rename is safe to do.
578 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
578 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
579 os.close(fd)
579 os.close(fd)
580 os.unlink(temp)
580 os.unlink(temp)
581 os.rename(dst, temp)
581 os.rename(dst, temp)
582 os.unlink(temp)
582 os.unlink(temp)
583 os.rename(src, dst)
583 os.rename(src, dst)
584
584
585 def unlink(f):
585 def unlink(f):
586 """unlink and remove the directory if it is empty"""
586 """unlink and remove the directory if it is empty"""
587 os.unlink(f)
587 os.unlink(f)
588 # try removing directories that might now be empty
588 # try removing directories that might now be empty
589 try:
589 try:
590 os.removedirs(os.path.dirname(f))
590 os.removedirs(os.path.dirname(f))
591 except OSError:
591 except OSError:
592 pass
592 pass
593
593
594 def copyfile(src, dest):
594 def copyfile(src, dest):
595 "copy a file, preserving mode"
595 "copy a file, preserving mode"
596 try:
596 try:
597 shutil.copyfile(src, dest)
597 shutil.copyfile(src, dest)
598 shutil.copymode(src, dest)
598 shutil.copymode(src, dest)
599 except shutil.Error, inst:
599 except shutil.Error, inst:
600 raise Abort(str(inst))
600 raise Abort(str(inst))
601
601
602 def copyfiles(src, dst, hardlink=None):
602 def copyfiles(src, dst, hardlink=None):
603 """Copy a directory tree using hardlinks if possible"""
603 """Copy a directory tree using hardlinks if possible"""
604
604
605 if hardlink is None:
605 if hardlink is None:
606 hardlink = (os.stat(src).st_dev ==
606 hardlink = (os.stat(src).st_dev ==
607 os.stat(os.path.dirname(dst)).st_dev)
607 os.stat(os.path.dirname(dst)).st_dev)
608
608
609 if os.path.isdir(src):
609 if os.path.isdir(src):
610 os.mkdir(dst)
610 os.mkdir(dst)
611 for name in os.listdir(src):
611 for name in os.listdir(src):
612 srcname = os.path.join(src, name)
612 srcname = os.path.join(src, name)
613 dstname = os.path.join(dst, name)
613 dstname = os.path.join(dst, name)
614 copyfiles(srcname, dstname, hardlink)
614 copyfiles(srcname, dstname, hardlink)
615 else:
615 else:
616 if hardlink:
616 if hardlink:
617 try:
617 try:
618 os_link(src, dst)
618 os_link(src, dst)
619 except (IOError, OSError):
619 except (IOError, OSError):
620 hardlink = False
620 hardlink = False
621 shutil.copy(src, dst)
621 shutil.copy(src, dst)
622 else:
622 else:
623 shutil.copy(src, dst)
623 shutil.copy(src, dst)
624
624
625 def audit_path(path):
625 def audit_path(path):
626 """Abort if path contains dangerous components"""
626 """Abort if path contains dangerous components"""
627 parts = os.path.normcase(path).split(os.sep)
627 parts = os.path.normcase(path).split(os.sep)
628 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
628 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
629 or os.pardir in parts):
629 or os.pardir in parts):
630 raise Abort(_("path contains illegal component: %s\n") % path)
630 raise Abort(_("path contains illegal component: %s\n") % path)
631
631
632 def _makelock_file(info, pathname):
632 def _makelock_file(info, pathname):
633 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
633 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
634 os.write(ld, info)
634 os.write(ld, info)
635 os.close(ld)
635 os.close(ld)
636
636
637 def _readlock_file(pathname):
637 def _readlock_file(pathname):
638 return posixfile(pathname).read()
638 return posixfile(pathname).read()
639
639
640 def nlinks(pathname):
640 def nlinks(pathname):
641 """Return number of hardlinks for the given file."""
641 """Return number of hardlinks for the given file."""
642 return os.lstat(pathname).st_nlink
642 return os.lstat(pathname).st_nlink
643
643
644 if hasattr(os, 'link'):
644 if hasattr(os, 'link'):
645 os_link = os.link
645 os_link = os.link
646 else:
646 else:
647 def os_link(src, dst):
647 def os_link(src, dst):
648 raise OSError(0, _("Hardlinks not supported"))
648 raise OSError(0, _("Hardlinks not supported"))
649
649
650 def fstat(fp):
650 def fstat(fp):
651 '''stat file object that may not have fileno method.'''
651 '''stat file object that may not have fileno method.'''
652 try:
652 try:
653 return os.fstat(fp.fileno())
653 return os.fstat(fp.fileno())
654 except AttributeError:
654 except AttributeError:
655 return os.stat(fp.name)
655 return os.stat(fp.name)
656
656
657 posixfile = file
657 posixfile = file
658
658
659 def is_win_9x():
659 def is_win_9x():
660 '''return true if run on windows 95, 98 or me.'''
660 '''return true if run on windows 95, 98 or me.'''
661 try:
661 try:
662 return sys.getwindowsversion()[3] == 1
662 return sys.getwindowsversion()[3] == 1
663 except AttributeError:
663 except AttributeError:
664 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
664 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
665
665
666 getuser_fallback = None
666 getuser_fallback = None
667
667
668 def getuser():
668 def getuser():
669 '''return name of current user'''
669 '''return name of current user'''
670 try:
670 try:
671 return getpass.getuser()
671 return getpass.getuser()
672 except ImportError:
672 except ImportError:
673 # import of pwd will fail on windows - try fallback
673 # import of pwd will fail on windows - try fallback
674 if getuser_fallback:
674 if getuser_fallback:
675 return getuser_fallback()
675 return getuser_fallback()
676 # raised if win32api not available
676 # raised if win32api not available
677 raise Abort(_('user name not available - set USERNAME '
677 raise Abort(_('user name not available - set USERNAME '
678 'environment variable'))
678 'environment variable'))
679
679
680 def username(uid=None):
680 def username(uid=None):
681 """Return the name of the user with the given uid.
681 """Return the name of the user with the given uid.
682
682
683 If uid is None, return the name of the current user."""
683 If uid is None, return the name of the current user."""
684 try:
684 try:
685 import pwd
685 import pwd
686 if uid is None:
686 if uid is None:
687 uid = os.getuid()
687 uid = os.getuid()
688 try:
688 try:
689 return pwd.getpwuid(uid)[0]
689 return pwd.getpwuid(uid)[0]
690 except KeyError:
690 except KeyError:
691 return str(uid)
691 return str(uid)
692 except ImportError:
692 except ImportError:
693 return None
693 return None
694
694
695 def groupname(gid=None):
695 def groupname(gid=None):
696 """Return the name of the group with the given gid.
696 """Return the name of the group with the given gid.
697
697
698 If gid is None, return the name of the current group."""
698 If gid is None, return the name of the current group."""
699 try:
699 try:
700 import grp
700 import grp
701 if gid is None:
701 if gid is None:
702 gid = os.getgid()
702 gid = os.getgid()
703 try:
703 try:
704 return grp.getgrgid(gid)[0]
704 return grp.getgrgid(gid)[0]
705 except KeyError:
705 except KeyError:
706 return str(gid)
706 return str(gid)
707 except ImportError:
707 except ImportError:
708 return None
708 return None
709
709
710 # File system features
710 # File system features
711
711
712 def checkfolding(path):
712 def checkfolding(path):
713 """
713 """
714 Check whether the given path is on a case-sensitive filesystem
714 Check whether the given path is on a case-sensitive filesystem
715
715
716 Requires a path (like /foo/.hg) ending with a foldable final
716 Requires a path (like /foo/.hg) ending with a foldable final
717 directory component.
717 directory component.
718 """
718 """
719 s1 = os.stat(path)
719 s1 = os.stat(path)
720 d, b = os.path.split(path)
720 d, b = os.path.split(path)
721 p2 = os.path.join(d, b.upper())
721 p2 = os.path.join(d, b.upper())
722 if path == p2:
722 if path == p2:
723 p2 = os.path.join(d, b.lower())
723 p2 = os.path.join(d, b.lower())
724 try:
724 try:
725 s2 = os.stat(p2)
725 s2 = os.stat(p2)
726 if s2 == s1:
726 if s2 == s1:
727 return False
727 return False
728 return True
728 return True
729 except:
729 except:
730 return True
730 return True
731
731
732 # Platform specific variants
732 # Platform specific variants
733 if os.name == 'nt':
733 if os.name == 'nt':
734 demandload(globals(), "msvcrt")
734 demandload(globals(), "msvcrt")
735 nulldev = 'NUL:'
735 nulldev = 'NUL:'
736
736
737 class winstdout:
737 class winstdout:
738 '''stdout on windows misbehaves if sent through a pipe'''
738 '''stdout on windows misbehaves if sent through a pipe'''
739
739
740 def __init__(self, fp):
740 def __init__(self, fp):
741 self.fp = fp
741 self.fp = fp
742
742
743 def __getattr__(self, key):
743 def __getattr__(self, key):
744 return getattr(self.fp, key)
744 return getattr(self.fp, key)
745
745
746 def close(self):
746 def close(self):
747 try:
747 try:
748 self.fp.close()
748 self.fp.close()
749 except: pass
749 except: pass
750
750
751 def write(self, s):
751 def write(self, s):
752 try:
752 try:
753 return self.fp.write(s)
753 return self.fp.write(s)
754 except IOError, inst:
754 except IOError, inst:
755 if inst.errno != 0: raise
755 if inst.errno != 0: raise
756 self.close()
756 self.close()
757 raise IOError(errno.EPIPE, 'Broken pipe')
757 raise IOError(errno.EPIPE, 'Broken pipe')
758
758
759 def flush(self):
759 def flush(self):
760 try:
760 try:
761 return self.fp.flush()
761 return self.fp.flush()
762 except IOError, inst:
762 except IOError, inst:
763 if inst.errno != errno.EINVAL: raise
763 if inst.errno != errno.EINVAL: raise
764 self.close()
764 self.close()
765 raise IOError(errno.EPIPE, 'Broken pipe')
765 raise IOError(errno.EPIPE, 'Broken pipe')
766
766
767 sys.stdout = winstdout(sys.stdout)
767 sys.stdout = winstdout(sys.stdout)
768
768
769 def system_rcpath():
769 def system_rcpath():
770 try:
770 try:
771 return system_rcpath_win32()
771 return system_rcpath_win32()
772 except:
772 except:
773 return [r'c:\mercurial\mercurial.ini']
773 return [r'c:\mercurial\mercurial.ini']
774
774
775 def os_rcpath():
775 def os_rcpath():
776 '''return default os-specific hgrc search path'''
776 '''return default os-specific hgrc search path'''
777 path = system_rcpath()
777 path = system_rcpath()
778 path.append(user_rcpath())
778 path.append(user_rcpath())
779 userprofile = os.environ.get('USERPROFILE')
779 userprofile = os.environ.get('USERPROFILE')
780 if userprofile:
780 if userprofile:
781 path.append(os.path.join(userprofile, 'mercurial.ini'))
781 path.append(os.path.join(userprofile, 'mercurial.ini'))
782 return path
782 return path
783
783
784 def user_rcpath():
784 def user_rcpath():
785 '''return os-specific hgrc search path to the user dir'''
785 '''return os-specific hgrc search path to the user dir'''
786 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
786 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
787
787
788 def parse_patch_output(output_line):
788 def parse_patch_output(output_line):
789 """parses the output produced by patch and returns the file name"""
789 """parses the output produced by patch and returns the file name"""
790 pf = output_line[14:]
790 pf = output_line[14:]
791 if pf[0] == '`':
791 if pf[0] == '`':
792 pf = pf[1:-1] # Remove the quotes
792 pf = pf[1:-1] # Remove the quotes
793 return pf
793 return pf
794
794
795 def testpid(pid):
795 def testpid(pid):
796 '''return False if pid dead, True if running or not known'''
796 '''return False if pid dead, True if running or not known'''
797 return True
797 return True
798
798
799 def is_exec(f, last):
799 def is_exec(f, last):
800 return last
800 return last
801
801
802 def set_exec(f, mode):
802 def set_exec(f, mode):
803 pass
803 pass
804
804
805 def set_binary(fd):
805 def set_binary(fd):
806 msvcrt.setmode(fd.fileno(), os.O_BINARY)
806 msvcrt.setmode(fd.fileno(), os.O_BINARY)
807
807
808 def pconvert(path):
808 def pconvert(path):
809 return path.replace("\\", "/")
809 return path.replace("\\", "/")
810
810
811 def localpath(path):
811 def localpath(path):
812 return path.replace('/', '\\')
812 return path.replace('/', '\\')
813
813
814 def normpath(path):
814 def normpath(path):
815 return pconvert(os.path.normpath(path))
815 return pconvert(os.path.normpath(path))
816
816
817 makelock = _makelock_file
817 makelock = _makelock_file
818 readlock = _readlock_file
818 readlock = _readlock_file
819
819
820 def samestat(s1, s2):
820 def samestat(s1, s2):
821 return False
821 return False
822
822
823 # A sequence of backslashes is special iff it precedes a double quote:
823 # A sequence of backslashes is special iff it precedes a double quote:
824 # - if there's an even number of backslashes, the double quote is not
824 # - if there's an even number of backslashes, the double quote is not
825 # quoted (i.e. it ends the quoted region)
825 # quoted (i.e. it ends the quoted region)
826 # - if there's an odd number of backslashes, the double quote is quoted
826 # - if there's an odd number of backslashes, the double quote is quoted
827 # - in both cases, every pair of backslashes is unquoted into a single
827 # - in both cases, every pair of backslashes is unquoted into a single
828 # backslash
828 # backslash
829 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
829 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
830 # So, to quote a string, we must surround it in double quotes, double
830 # So, to quote a string, we must surround it in double quotes, double
831 # the number of backslashes that preceed double quotes and add another
831 # the number of backslashes that preceed double quotes and add another
832 # backslash before every double quote (being careful with the double
832 # backslash before every double quote (being careful with the double
833 # quote we've appended to the end)
833 # quote we've appended to the end)
834 _quotere = None
834 _quotere = None
835 def shellquote(s):
835 def shellquote(s):
836 global _quotere
836 global _quotere
837 if _quotere is None:
837 if _quotere is None:
838 _quotere = re.compile(r'(\\*)("|\\$)')
838 _quotere = re.compile(r'(\\*)("|\\$)')
839 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
839 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
840
840
841 def explain_exit(code):
841 def explain_exit(code):
842 return _("exited with status %d") % code, code
842 return _("exited with status %d") % code, code
843
843
844 # if you change this stub into a real check, please try to implement the
844 # if you change this stub into a real check, please try to implement the
845 # username and groupname functions above, too.
845 # username and groupname functions above, too.
846 def isowner(fp, st=None):
846 def isowner(fp, st=None):
847 return True
847 return True
848
848
849 try:
849 try:
850 # override functions with win32 versions if possible
850 # override functions with win32 versions if possible
851 from util_win32 import *
851 from util_win32 import *
852 if not is_win_9x():
852 if not is_win_9x():
853 posixfile = posixfile_nt
853 posixfile = posixfile_nt
854 except ImportError:
854 except ImportError:
855 pass
855 pass
856
856
857 else:
857 else:
858 nulldev = '/dev/null'
858 nulldev = '/dev/null'
859
859
860 def rcfiles(path):
860 def rcfiles(path):
861 rcs = [os.path.join(path, 'hgrc')]
861 rcs = [os.path.join(path, 'hgrc')]
862 rcdir = os.path.join(path, 'hgrc.d')
862 rcdir = os.path.join(path, 'hgrc.d')
863 try:
863 try:
864 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
864 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
865 if f.endswith(".rc")])
865 if f.endswith(".rc")])
866 except OSError:
866 except OSError:
867 pass
867 pass
868 return rcs
868 return rcs
869
869
870 def os_rcpath():
870 def os_rcpath():
871 '''return default os-specific hgrc search path'''
871 '''return default os-specific hgrc search path'''
872 path = []
872 path = []
873 # old mod_python does not set sys.argv
873 # old mod_python does not set sys.argv
874 if len(getattr(sys, 'argv', [])) > 0:
874 if len(getattr(sys, 'argv', [])) > 0:
875 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
875 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
876 '/../etc/mercurial'))
876 '/../etc/mercurial'))
877 path.extend(rcfiles('/etc/mercurial'))
877 path.extend(rcfiles('/etc/mercurial'))
878 path.append(os.path.expanduser('~/.hgrc'))
878 path.append(os.path.expanduser('~/.hgrc'))
879 path = [os.path.normpath(f) for f in path]
879 path = [os.path.normpath(f) for f in path]
880 return path
880 return path
881
881
882 def parse_patch_output(output_line):
882 def parse_patch_output(output_line):
883 """parses the output produced by patch and returns the file name"""
883 """parses the output produced by patch and returns the file name"""
884 pf = output_line[14:]
884 pf = output_line[14:]
885 if pf.startswith("'") and pf.endswith("'") and " " in pf:
885 if pf.startswith("'") and pf.endswith("'") and " " in pf:
886 pf = pf[1:-1] # Remove the quotes
886 pf = pf[1:-1] # Remove the quotes
887 return pf
887 return pf
888
888
889 def is_exec(f, last):
889 def is_exec(f, last):
890 """check whether a file is executable"""
890 """check whether a file is executable"""
891 return (os.lstat(f).st_mode & 0100 != 0)
891 return (os.lstat(f).st_mode & 0100 != 0)
892
892
893 def set_exec(f, mode):
893 def set_exec(f, mode):
894 s = os.lstat(f).st_mode
894 s = os.lstat(f).st_mode
895 if (s & 0100 != 0) == mode:
895 if (s & 0100 != 0) == mode:
896 return
896 return
897 if mode:
897 if mode:
898 # Turn on +x for every +r bit when making a file executable
898 # Turn on +x for every +r bit when making a file executable
899 # and obey umask.
899 # and obey umask.
900 umask = os.umask(0)
900 umask = os.umask(0)
901 os.umask(umask)
901 os.umask(umask)
902 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
902 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
903 else:
903 else:
904 os.chmod(f, s & 0666)
904 os.chmod(f, s & 0666)
905
905
906 def set_binary(fd):
906 def set_binary(fd):
907 pass
907 pass
908
908
909 def pconvert(path):
909 def pconvert(path):
910 return path
910 return path
911
911
912 def localpath(path):
912 def localpath(path):
913 return path
913 return path
914
914
915 normpath = os.path.normpath
915 normpath = os.path.normpath
916 samestat = os.path.samestat
916 samestat = os.path.samestat
917
917
918 def makelock(info, pathname):
918 def makelock(info, pathname):
919 try:
919 try:
920 os.symlink(info, pathname)
920 os.symlink(info, pathname)
921 except OSError, why:
921 except OSError, why:
922 if why.errno == errno.EEXIST:
922 if why.errno == errno.EEXIST:
923 raise
923 raise
924 else:
924 else:
925 _makelock_file(info, pathname)
925 _makelock_file(info, pathname)
926
926
927 def readlock(pathname):
927 def readlock(pathname):
928 try:
928 try:
929 return os.readlink(pathname)
929 return os.readlink(pathname)
930 except OSError, why:
930 except OSError, why:
931 if why.errno == errno.EINVAL:
931 if why.errno == errno.EINVAL:
932 return _readlock_file(pathname)
932 return _readlock_file(pathname)
933 else:
933 else:
934 raise
934 raise
935
935
936 def shellquote(s):
936 def shellquote(s):
937 return "'%s'" % s.replace("'", "'\\''")
937 return "'%s'" % s.replace("'", "'\\''")
938
938
939 def testpid(pid):
939 def testpid(pid):
940 '''return False if pid dead, True if running or not sure'''
940 '''return False if pid dead, True if running or not sure'''
941 try:
941 try:
942 os.kill(pid, 0)
942 os.kill(pid, 0)
943 return True
943 return True
944 except OSError, inst:
944 except OSError, inst:
945 return inst.errno != errno.ESRCH
945 return inst.errno != errno.ESRCH
946
946
947 def explain_exit(code):
947 def explain_exit(code):
948 """return a 2-tuple (desc, code) describing a process's status"""
948 """return a 2-tuple (desc, code) describing a process's status"""
949 if os.WIFEXITED(code):
949 if os.WIFEXITED(code):
950 val = os.WEXITSTATUS(code)
950 val = os.WEXITSTATUS(code)
951 return _("exited with status %d") % val, val
951 return _("exited with status %d") % val, val
952 elif os.WIFSIGNALED(code):
952 elif os.WIFSIGNALED(code):
953 val = os.WTERMSIG(code)
953 val = os.WTERMSIG(code)
954 return _("killed by signal %d") % val, val
954 return _("killed by signal %d") % val, val
955 elif os.WIFSTOPPED(code):
955 elif os.WIFSTOPPED(code):
956 val = os.WSTOPSIG(code)
956 val = os.WSTOPSIG(code)
957 return _("stopped by signal %d") % val, val
957 return _("stopped by signal %d") % val, val
958 raise ValueError(_("invalid exit code"))
958 raise ValueError(_("invalid exit code"))
959
959
960 def isowner(fp, st=None):
960 def isowner(fp, st=None):
961 """Return True if the file object f belongs to the current user.
961 """Return True if the file object f belongs to the current user.
962
962
963 The return value of a util.fstat(f) may be passed as the st argument.
963 The return value of a util.fstat(f) may be passed as the st argument.
964 """
964 """
965 if st is None:
965 if st is None:
966 st = fstat(fp)
966 st = fstat(fp)
967 return st.st_uid == os.getuid()
967 return st.st_uid == os.getuid()
968
968
969 def _buildencodefun():
969 def _buildencodefun():
970 e = '_'
970 e = '_'
971 win_reserved = [ord(x) for x in '\\:*?"<>|']
971 win_reserved = [ord(x) for x in '\\:*?"<>|']
972 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
972 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
973 for x in (range(32) + range(126, 256) + win_reserved):
973 for x in (range(32) + range(126, 256) + win_reserved):
974 cmap[chr(x)] = "~%02x" % x
974 cmap[chr(x)] = "~%02x" % x
975 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
975 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
976 cmap[chr(x)] = e + chr(x).lower()
976 cmap[chr(x)] = e + chr(x).lower()
977 dmap = {}
977 dmap = {}
978 for k, v in cmap.iteritems():
978 for k, v in cmap.iteritems():
979 dmap[v] = k
979 dmap[v] = k
980 def decode(s):
980 def decode(s):
981 i = 0
981 i = 0
982 while i < len(s):
982 while i < len(s):
983 for l in xrange(1, 4):
983 for l in xrange(1, 4):
984 try:
984 try:
985 yield dmap[s[i:i+l]]
985 yield dmap[s[i:i+l]]
986 i += l
986 i += l
987 break
987 break
988 except KeyError:
988 except KeyError:
989 pass
989 pass
990 else:
990 else:
991 raise KeyError
991 raise KeyError
992 return (lambda s: "".join([cmap[c] for c in s]),
992 return (lambda s: "".join([cmap[c] for c in s]),
993 lambda s: "".join(list(decode(s))))
993 lambda s: "".join(list(decode(s))))
994
994
995 encodefilename, decodefilename = _buildencodefun()
995 encodefilename, decodefilename = _buildencodefun()
996
996
997 def encodedopener(openerfn, fn):
997 def encodedopener(openerfn, fn):
998 def o(path, *args, **kw):
998 def o(path, *args, **kw):
999 return openerfn(fn(path), *args, **kw)
999 return openerfn(fn(path), *args, **kw)
1000 return o
1000 return o
1001
1001
1002 def opener(base, audit=True):
1002 def opener(base, audit=True):
1003 """
1003 """
1004 return a function that opens files relative to base
1004 return a function that opens files relative to base
1005
1005
1006 this function is used to hide the details of COW semantics and
1006 this function is used to hide the details of COW semantics and
1007 remote file access from higher level code.
1007 remote file access from higher level code.
1008 """
1008 """
1009 p = base
1009 p = base
1010 audit_p = audit
1010 audit_p = audit
1011
1011
1012 def mktempcopy(name):
1012 def mktempcopy(name):
1013 d, fn = os.path.split(name)
1013 d, fn = os.path.split(name)
1014 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1014 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1015 os.close(fd)
1015 os.close(fd)
1016 ofp = posixfile(temp, "wb")
1016 ofp = posixfile(temp, "wb")
1017 try:
1017 try:
1018 try:
1018 try:
1019 ifp = posixfile(name, "rb")
1019 ifp = posixfile(name, "rb")
1020 except IOError, inst:
1020 except IOError, inst:
1021 if not getattr(inst, 'filename', None):
1021 if not getattr(inst, 'filename', None):
1022 inst.filename = name
1022 inst.filename = name
1023 raise
1023 raise
1024 for chunk in filechunkiter(ifp):
1024 for chunk in filechunkiter(ifp):
1025 ofp.write(chunk)
1025 ofp.write(chunk)
1026 ifp.close()
1026 ifp.close()
1027 ofp.close()
1027 ofp.close()
1028 except:
1028 except:
1029 try: os.unlink(temp)
1029 try: os.unlink(temp)
1030 except: pass
1030 except: pass
1031 raise
1031 raise
1032 st = os.lstat(name)
1032 st = os.lstat(name)
1033 os.chmod(temp, st.st_mode)
1033 os.chmod(temp, st.st_mode)
1034 return temp
1034 return temp
1035
1035
1036 class atomictempfile(posixfile):
1036 class atomictempfile(posixfile):
1037 """the file will only be copied when rename is called"""
1037 """the file will only be copied when rename is called"""
1038 def __init__(self, name, mode):
1038 def __init__(self, name, mode):
1039 self.__name = name
1039 self.__name = name
1040 self.temp = mktempcopy(name)
1040 self.temp = mktempcopy(name)
1041 posixfile.__init__(self, self.temp, mode)
1041 posixfile.__init__(self, self.temp, mode)
1042 def rename(self):
1042 def rename(self):
1043 if not self.closed:
1043 if not self.closed:
1044 posixfile.close(self)
1044 posixfile.close(self)
1045 rename(self.temp, localpath(self.__name))
1045 rename(self.temp, localpath(self.__name))
1046 def __del__(self):
1046 def __del__(self):
1047 if not self.closed:
1047 if not self.closed:
1048 try:
1048 try:
1049 os.unlink(self.temp)
1049 os.unlink(self.temp)
1050 except: pass
1050 except: pass
1051 posixfile.close(self)
1051 posixfile.close(self)
1052
1052
1053 class atomicfile(atomictempfile):
1053 class atomicfile(atomictempfile):
1054 """the file will only be copied on close"""
1054 """the file will only be copied on close"""
1055 def __init__(self, name, mode):
1055 def __init__(self, name, mode):
1056 atomictempfile.__init__(self, name, mode)
1056 atomictempfile.__init__(self, name, mode)
1057 def close(self):
1057 def close(self):
1058 self.rename()
1058 self.rename()
1059 def __del__(self):
1059 def __del__(self):
1060 self.rename()
1060 self.rename()
1061
1061
1062 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1062 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1063 if audit_p:
1063 if audit_p:
1064 audit_path(path)
1064 audit_path(path)
1065 f = os.path.join(p, path)
1065 f = os.path.join(p, path)
1066
1066
1067 if not text:
1067 if not text:
1068 mode += "b" # for that other OS
1068 mode += "b" # for that other OS
1069
1069
1070 if mode[0] != "r":
1070 if mode[0] != "r":
1071 try:
1071 try:
1072 nlink = nlinks(f)
1072 nlink = nlinks(f)
1073 except OSError:
1073 except OSError:
1074 d = os.path.dirname(f)
1074 d = os.path.dirname(f)
1075 if not os.path.isdir(d):
1075 if not os.path.isdir(d):
1076 os.makedirs(d)
1076 os.makedirs(d)
1077 else:
1077 else:
1078 if atomic:
1078 if atomic:
1079 return atomicfile(f, mode)
1079 return atomicfile(f, mode)
1080 elif atomictemp:
1080 elif atomictemp:
1081 return atomictempfile(f, mode)
1081 return atomictempfile(f, mode)
1082 if nlink > 1:
1082 if nlink > 1:
1083 rename(mktempcopy(f), f)
1083 rename(mktempcopy(f), f)
1084 return posixfile(f, mode)
1084 return posixfile(f, mode)
1085
1085
1086 return o
1086 return o
1087
1087
1088 class chunkbuffer(object):
1088 class chunkbuffer(object):
1089 """Allow arbitrary sized chunks of data to be efficiently read from an
1089 """Allow arbitrary sized chunks of data to be efficiently read from an
1090 iterator over chunks of arbitrary size."""
1090 iterator over chunks of arbitrary size."""
1091
1091
1092 def __init__(self, in_iter, targetsize = 2**16):
1092 def __init__(self, in_iter, targetsize = 2**16):
1093 """in_iter is the iterator that's iterating over the input chunks.
1093 """in_iter is the iterator that's iterating over the input chunks.
1094 targetsize is how big a buffer to try to maintain."""
1094 targetsize is how big a buffer to try to maintain."""
1095 self.in_iter = iter(in_iter)
1095 self.in_iter = iter(in_iter)
1096 self.buf = ''
1096 self.buf = ''
1097 self.targetsize = int(targetsize)
1097 self.targetsize = int(targetsize)
1098 if self.targetsize <= 0:
1098 if self.targetsize <= 0:
1099 raise ValueError(_("targetsize must be greater than 0, was %d") %
1099 raise ValueError(_("targetsize must be greater than 0, was %d") %
1100 targetsize)
1100 targetsize)
1101 self.iterempty = False
1101 self.iterempty = False
1102
1102
1103 def fillbuf(self):
1103 def fillbuf(self):
1104 """Ignore target size; read every chunk from iterator until empty."""
1104 """Ignore target size; read every chunk from iterator until empty."""
1105 if not self.iterempty:
1105 if not self.iterempty:
1106 collector = cStringIO.StringIO()
1106 collector = cStringIO.StringIO()
1107 collector.write(self.buf)
1107 collector.write(self.buf)
1108 for ch in self.in_iter:
1108 for ch in self.in_iter:
1109 collector.write(ch)
1109 collector.write(ch)
1110 self.buf = collector.getvalue()
1110 self.buf = collector.getvalue()
1111 self.iterempty = True
1111 self.iterempty = True
1112
1112
1113 def read(self, l):
1113 def read(self, l):
1114 """Read L bytes of data from the iterator of chunks of data.
1114 """Read L bytes of data from the iterator of chunks of data.
1115 Returns less than L bytes if the iterator runs dry."""
1115 Returns less than L bytes if the iterator runs dry."""
1116 if l > len(self.buf) and not self.iterempty:
1116 if l > len(self.buf) and not self.iterempty:
1117 # Clamp to a multiple of self.targetsize
1117 # Clamp to a multiple of self.targetsize
1118 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1118 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1119 collector = cStringIO.StringIO()
1119 collector = cStringIO.StringIO()
1120 collector.write(self.buf)
1120 collector.write(self.buf)
1121 collected = len(self.buf)
1121 collected = len(self.buf)
1122 for chunk in self.in_iter:
1122 for chunk in self.in_iter:
1123 collector.write(chunk)
1123 collector.write(chunk)
1124 collected += len(chunk)
1124 collected += len(chunk)
1125 if collected >= targetsize:
1125 if collected >= targetsize:
1126 break
1126 break
1127 if collected < targetsize:
1127 if collected < targetsize:
1128 self.iterempty = True
1128 self.iterempty = True
1129 self.buf = collector.getvalue()
1129 self.buf = collector.getvalue()
1130 s, self.buf = self.buf[:l], buffer(self.buf, l)
1130 s, self.buf = self.buf[:l], buffer(self.buf, l)
1131 return s
1131 return s
1132
1132
1133 def filechunkiter(f, size=65536, limit=None):
1133 def filechunkiter(f, size=65536, limit=None):
1134 """Create a generator that produces the data in the file size
1134 """Create a generator that produces the data in the file size
1135 (default 65536) bytes at a time, up to optional limit (default is
1135 (default 65536) bytes at a time, up to optional limit (default is
1136 to read all data). Chunks may be less than size bytes if the
1136 to read all data). Chunks may be less than size bytes if the
1137 chunk is the last chunk in the file, or the file is a socket or
1137 chunk is the last chunk in the file, or the file is a socket or
1138 some other type of file that sometimes reads less data than is
1138 some other type of file that sometimes reads less data than is
1139 requested."""
1139 requested."""
1140 assert size >= 0
1140 assert size >= 0
1141 assert limit is None or limit >= 0
1141 assert limit is None or limit >= 0
1142 while True:
1142 while True:
1143 if limit is None: nbytes = size
1143 if limit is None: nbytes = size
1144 else: nbytes = min(limit, size)
1144 else: nbytes = min(limit, size)
1145 s = nbytes and f.read(nbytes)
1145 s = nbytes and f.read(nbytes)
1146 if not s: break
1146 if not s: break
1147 if limit: limit -= len(s)
1147 if limit: limit -= len(s)
1148 yield s
1148 yield s
1149
1149
1150 def makedate():
1150 def makedate():
1151 lt = time.localtime()
1151 lt = time.localtime()
1152 if lt[8] == 1 and time.daylight:
1152 if lt[8] == 1 and time.daylight:
1153 tz = time.altzone
1153 tz = time.altzone
1154 else:
1154 else:
1155 tz = time.timezone
1155 tz = time.timezone
1156 return time.mktime(lt), tz
1156 return time.mktime(lt), tz
1157
1157
1158 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1158 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1159 """represent a (unixtime, offset) tuple as a localized time.
1159 """represent a (unixtime, offset) tuple as a localized time.
1160 unixtime is seconds since the epoch, and offset is the time zone's
1160 unixtime is seconds since the epoch, and offset is the time zone's
1161 number of seconds away from UTC. if timezone is false, do not
1161 number of seconds away from UTC. if timezone is false, do not
1162 append time zone to string."""
1162 append time zone to string."""
1163 t, tz = date or makedate()
1163 t, tz = date or makedate()
1164 s = time.strftime(format, time.gmtime(float(t) - tz))
1164 s = time.strftime(format, time.gmtime(float(t) - tz))
1165 if timezone:
1165 if timezone:
1166 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1166 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1167 return s
1167 return s
1168
1168
1169 def strdate(string, format, defaults):
1169 def strdate(string, format, defaults):
1170 """parse a localized time string and return a (unixtime, offset) tuple.
1170 """parse a localized time string and return a (unixtime, offset) tuple.
1171 if the string cannot be parsed, ValueError is raised."""
1171 if the string cannot be parsed, ValueError is raised."""
1172 def timezone(string):
1172 def timezone(string):
1173 tz = string.split()[-1]
1173 tz = string.split()[-1]
1174 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1174 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1175 tz = int(tz)
1175 tz = int(tz)
1176 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1176 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1177 return offset
1177 return offset
1178 if tz == "GMT" or tz == "UTC":
1178 if tz == "GMT" or tz == "UTC":
1179 return 0
1179 return 0
1180 return None
1180 return None
1181
1181
1182 # NOTE: unixtime = localunixtime + offset
1182 # NOTE: unixtime = localunixtime + offset
1183 offset, date = timezone(string), string
1183 offset, date = timezone(string), string
1184 if offset != None:
1184 if offset != None:
1185 date = " ".join(string.split()[:-1])
1185 date = " ".join(string.split()[:-1])
1186
1186
1187 # add missing elements from defaults
1187 # add missing elements from defaults
1188 for part in defaults:
1188 for part in defaults:
1189 found = [True for p in part if ("%"+p) in format]
1189 found = [True for p in part if ("%"+p) in format]
1190 if not found:
1190 if not found:
1191 date += "@" + defaults[part]
1191 date += "@" + defaults[part]
1192 format += "@%" + part[0]
1192 format += "@%" + part[0]
1193
1193
1194 timetuple = time.strptime(date, format)
1194 timetuple = time.strptime(date, format)
1195 localunixtime = int(calendar.timegm(timetuple))
1195 localunixtime = int(calendar.timegm(timetuple))
1196 if offset is None:
1196 if offset is None:
1197 # local timezone
1197 # local timezone
1198 unixtime = int(time.mktime(timetuple))
1198 unixtime = int(time.mktime(timetuple))
1199 offset = unixtime - localunixtime
1199 offset = unixtime - localunixtime
1200 else:
1200 else:
1201 unixtime = localunixtime + offset
1201 unixtime = localunixtime + offset
1202 return unixtime, offset
1202 return unixtime, offset
1203
1203
1204 def parsedate(string, formats=None, defaults=None):
1204 def parsedate(string, formats=None, defaults=None):
1205 """parse a localized time string and return a (unixtime, offset) tuple.
1205 """parse a localized time string and return a (unixtime, offset) tuple.
1206 The date may be a "unixtime offset" string or in one of the specified
1206 The date may be a "unixtime offset" string or in one of the specified
1207 formats."""
1207 formats."""
1208 if not string:
1208 if not string:
1209 return 0, 0
1209 return 0, 0
1210 if not formats:
1210 if not formats:
1211 formats = defaultdateformats
1211 formats = defaultdateformats
1212 string = string.strip()
1212 string = string.strip()
1213 try:
1213 try:
1214 when, offset = map(int, string.split(' '))
1214 when, offset = map(int, string.split(' '))
1215 except ValueError:
1215 except ValueError:
1216 # fill out defaults
1216 # fill out defaults
1217 if not defaults:
1217 if not defaults:
1218 defaults = {}
1218 defaults = {}
1219 now = makedate()
1219 now = makedate()
1220 for part in "d mb yY HI M S".split():
1220 for part in "d mb yY HI M S".split():
1221 if part not in defaults:
1221 if part not in defaults:
1222 if part[0] in "HMS":
1222 if part[0] in "HMS":
1223 defaults[part] = "00"
1223 defaults[part] = "00"
1224 elif part[0] in "dm":
1224 elif part[0] in "dm":
1225 defaults[part] = "1"
1225 defaults[part] = "1"
1226 else:
1226 else:
1227 defaults[part] = datestr(now, "%" + part[0], False)
1227 defaults[part] = datestr(now, "%" + part[0], False)
1228
1228
1229 for format in formats:
1229 for format in formats:
1230 try:
1230 try:
1231 when, offset = strdate(string, format, defaults)
1231 when, offset = strdate(string, format, defaults)
1232 except ValueError:
1232 except ValueError:
1233 pass
1233 pass
1234 else:
1234 else:
1235 break
1235 break
1236 else:
1236 else:
1237 raise Abort(_('invalid date: %r ') % string)
1237 raise Abort(_('invalid date: %r ') % string)
1238 # validate explicit (probably user-specified) date and
1238 # validate explicit (probably user-specified) date and
1239 # time zone offset. values must fit in signed 32 bits for
1239 # time zone offset. values must fit in signed 32 bits for
1240 # current 32-bit linux runtimes. timezones go from UTC-12
1240 # current 32-bit linux runtimes. timezones go from UTC-12
1241 # to UTC+14
1241 # to UTC+14
1242 if abs(when) > 0x7fffffff:
1242 if abs(when) > 0x7fffffff:
1243 raise Abort(_('date exceeds 32 bits: %d') % when)
1243 raise Abort(_('date exceeds 32 bits: %d') % when)
1244 if offset < -50400 or offset > 43200:
1244 if offset < -50400 or offset > 43200:
1245 raise Abort(_('impossible time zone offset: %d') % offset)
1245 raise Abort(_('impossible time zone offset: %d') % offset)
1246 return when, offset
1246 return when, offset
1247
1247
1248 def matchdate(date):
1248 def matchdate(date):
1249 """Return a function that matches a given date match specifier
1249 """Return a function that matches a given date match specifier
1250
1250
1251 Formats include:
1251 Formats include:
1252
1252
1253 '{date}' match a given date to the accuracy provided
1253 '{date}' match a given date to the accuracy provided
1254
1254
1255 '<{date}' on or before a given date
1255 '<{date}' on or before a given date
1256
1256
1257 '>{date}' on or after a given date
1257 '>{date}' on or after a given date
1258
1258
1259 """
1259 """
1260
1260
1261 def lower(date):
1261 def lower(date):
1262 return parsedate(date, extendeddateformats)[0]
1262 return parsedate(date, extendeddateformats)[0]
1263
1263
1264 def upper(date):
1264 def upper(date):
1265 d = dict(mb="12", HI="23", M="59", S="59")
1265 d = dict(mb="12", HI="23", M="59", S="59")
1266 for days in "31 30 29".split():
1266 for days in "31 30 29".split():
1267 try:
1267 try:
1268 d["d"] = days
1268 d["d"] = days
1269 return parsedate(date, extendeddateformats, d)[0]
1269 return parsedate(date, extendeddateformats, d)[0]
1270 except:
1270 except:
1271 pass
1271 pass
1272 d["d"] = "28"
1272 d["d"] = "28"
1273 return parsedate(date, extendeddateformats, d)[0]
1273 return parsedate(date, extendeddateformats, d)[0]
1274
1274
1275 if date[0] == "<":
1275 if date[0] == "<":
1276 when = upper(date[1:])
1276 when = upper(date[1:])
1277 return lambda x: x <= when
1277 return lambda x: x <= when
1278 elif date[0] == ">":
1278 elif date[0] == ">":
1279 when = lower(date[1:])
1279 when = lower(date[1:])
1280 return lambda x: x >= when
1280 return lambda x: x >= when
1281 elif date[0] == "-":
1281 elif date[0] == "-":
1282 try:
1282 try:
1283 days = int(date[1:])
1283 days = int(date[1:])
1284 except ValueError:
1284 except ValueError:
1285 raise Abort(_("invalid day spec: %s") % date[1:])
1285 raise Abort(_("invalid day spec: %s") % date[1:])
1286 when = makedate()[0] - days * 3600 * 24
1286 when = makedate()[0] - days * 3600 * 24
1287 return lambda x: x >= when
1287 return lambda x: x >= when
1288 elif " to " in date:
1288 elif " to " in date:
1289 a, b = date.split(" to ")
1289 a, b = date.split(" to ")
1290 start, stop = lower(a), upper(b)
1290 start, stop = lower(a), upper(b)
1291 return lambda x: x >= start and x <= stop
1291 return lambda x: x >= start and x <= stop
1292 else:
1292 else:
1293 start, stop = lower(date), upper(date)
1293 start, stop = lower(date), upper(date)
1294 return lambda x: x >= start and x <= stop
1294 return lambda x: x >= start and x <= stop
1295
1295
1296 def shortuser(user):
1296 def shortuser(user):
1297 """Return a short representation of a user name or email address."""
1297 """Return a short representation of a user name or email address."""
1298 f = user.find('@')
1298 f = user.find('@')
1299 if f >= 0:
1299 if f >= 0:
1300 user = user[:f]
1300 user = user[:f]
1301 f = user.find('<')
1301 f = user.find('<')
1302 if f >= 0:
1302 if f >= 0:
1303 user = user[f+1:]
1303 user = user[f+1:]
1304 f = user.find(' ')
1304 f = user.find(' ')
1305 if f >= 0:
1305 if f >= 0:
1306 user = user[:f]
1306 user = user[:f]
1307 f = user.find('.')
1307 f = user.find('.')
1308 if f >= 0:
1308 if f >= 0:
1309 user = user[:f]
1309 user = user[:f]
1310 return user
1310 return user
1311
1311
1312 def ellipsis(text, maxlength=400):
1312 def ellipsis(text, maxlength=400):
1313 """Trim string to at most maxlength (default: 400) characters."""
1313 """Trim string to at most maxlength (default: 400) characters."""
1314 if len(text) <= maxlength:
1314 if len(text) <= maxlength:
1315 return text
1315 return text
1316 else:
1316 else:
1317 return "%s..." % (text[:maxlength-3])
1317 return "%s..." % (text[:maxlength-3])
1318
1318
1319 def walkrepos(path):
1319 def walkrepos(path):
1320 '''yield every hg repository under path, recursively.'''
1320 '''yield every hg repository under path, recursively.'''
1321 def errhandler(err):
1321 def errhandler(err):
1322 if err.filename == path:
1322 if err.filename == path:
1323 raise err
1323 raise err
1324
1324
1325 for root, dirs, files in os.walk(path, onerror=errhandler):
1325 for root, dirs, files in os.walk(path, onerror=errhandler):
1326 for d in dirs:
1326 for d in dirs:
1327 if d == '.hg':
1327 if d == '.hg':
1328 yield root
1328 yield root
1329 dirs[:] = []
1329 dirs[:] = []
1330 break
1330 break
1331
1331
1332 _rcpath = None
1332 _rcpath = None
1333
1333
1334 def rcpath():
1334 def rcpath():
1335 '''return hgrc search path. if env var HGRCPATH is set, use it.
1335 '''return hgrc search path. if env var HGRCPATH is set, use it.
1336 for each item in path, if directory, use files ending in .rc,
1336 for each item in path, if directory, use files ending in .rc,
1337 else use item.
1337 else use item.
1338 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1338 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1339 if no HGRCPATH, use default os-specific path.'''
1339 if no HGRCPATH, use default os-specific path.'''
1340 global _rcpath
1340 global _rcpath
1341 if _rcpath is None:
1341 if _rcpath is None:
1342 if 'HGRCPATH' in os.environ:
1342 if 'HGRCPATH' in os.environ:
1343 _rcpath = []
1343 _rcpath = []
1344 for p in os.environ['HGRCPATH'].split(os.pathsep):
1344 for p in os.environ['HGRCPATH'].split(os.pathsep):
1345 if not p: continue
1345 if not p: continue
1346 if os.path.isdir(p):
1346 if os.path.isdir(p):
1347 for f in os.listdir(p):
1347 for f in os.listdir(p):
1348 if f.endswith('.rc'):
1348 if f.endswith('.rc'):
1349 _rcpath.append(os.path.join(p, f))
1349 _rcpath.append(os.path.join(p, f))
1350 else:
1350 else:
1351 _rcpath.append(p)
1351 _rcpath.append(p)
1352 else:
1352 else:
1353 _rcpath = os_rcpath()
1353 _rcpath = os_rcpath()
1354 return _rcpath
1354 return _rcpath
1355
1355
1356 def bytecount(nbytes):
1356 def bytecount(nbytes):
1357 '''return byte count formatted as readable string, with units'''
1357 '''return byte count formatted as readable string, with units'''
1358
1358
1359 units = (
1359 units = (
1360 (100, 1<<30, _('%.0f GB')),
1360 (100, 1<<30, _('%.0f GB')),
1361 (10, 1<<30, _('%.1f GB')),
1361 (10, 1<<30, _('%.1f GB')),
1362 (1, 1<<30, _('%.2f GB')),
1362 (1, 1<<30, _('%.2f GB')),
1363 (100, 1<<20, _('%.0f MB')),
1363 (100, 1<<20, _('%.0f MB')),
1364 (10, 1<<20, _('%.1f MB')),
1364 (10, 1<<20, _('%.1f MB')),
1365 (1, 1<<20, _('%.2f MB')),
1365 (1, 1<<20, _('%.2f MB')),
1366 (100, 1<<10, _('%.0f KB')),
1366 (100, 1<<10, _('%.0f KB')),
1367 (10, 1<<10, _('%.1f KB')),
1367 (10, 1<<10, _('%.1f KB')),
1368 (1, 1<<10, _('%.2f KB')),
1368 (1, 1<<10, _('%.2f KB')),
1369 (1, 1, _('%.0f bytes')),
1369 (1, 1, _('%.0f bytes')),
1370 )
1370 )
1371
1371
1372 for multiplier, divisor, format in units:
1372 for multiplier, divisor, format in units:
1373 if nbytes >= divisor * multiplier:
1373 if nbytes >= divisor * multiplier:
1374 return format % (nbytes / float(divisor))
1374 return format % (nbytes / float(divisor))
1375 return units[-1][2] % nbytes
1375 return units[-1][2] % nbytes
1376
1376
1377 def drop_scheme(scheme, path):
1377 def drop_scheme(scheme, path):
1378 sc = scheme + ':'
1378 sc = scheme + ':'
1379 if path.startswith(sc):
1379 if path.startswith(sc):
1380 path = path[len(sc):]
1380 path = path[len(sc):]
1381 if path.startswith('//'):
1381 if path.startswith('//'):
1382 path = path[2:]
1382 path = path[2:]
1383 return path
1383 return path
@@ -1,92 +1,94
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init
3 hg init
4 echo 123 > a
4 echo 123 > a
5 echo 123 > c
5 echo 123 > c
6 echo 123 > e
6 echo 123 > e
7 hg add a c e
7 hg add a c e
8 hg commit -m "first" -d "1000000 0" a c e
8 hg commit -m "first" -d "1000000 0" a c e
9 echo 123 > b
9 echo 123 > b
10 echo %% should show b unknown
10 echo %% should show b unknown
11 hg status
11 hg status
12 echo 12 > c
12 echo 12 > c
13 echo %% should show b unknown and c modified
13 echo %% should show b unknown and c modified
14 hg status
14 hg status
15 hg add b
15 hg add b
16 echo %% should show b added and c modified
16 echo %% should show b added and c modified
17 hg status
17 hg status
18 hg rm a
18 hg rm a
19 echo %% should show a removed, b added and c modified
19 echo %% should show a removed, b added and c modified
20 hg status
20 hg status
21 hg revert a
21 hg revert a
22 echo %% should show b added, copy saved, and c modified
22 echo %% should show b added, copy saved, and c modified
23 hg status
23 hg status
24 hg revert b
24 hg revert b
25 echo %% should show b unknown, and c modified
25 echo %% should show b unknown, and c modified
26 hg status
26 hg status
27 hg revert --no-backup c
27 hg revert --no-backup c
28 echo %% should show unknown: b
28 echo %% should show unknown: b
29 hg status
29 hg status
30 echo %% should show a b c e
30 echo %% should show a b c e
31 ls
31 ls
32 echo %% should verbosely save backup to e.orig
32 echo %% should verbosely save backup to e.orig
33 echo z > e
33 echo z > e
34 hg revert --all -v
34 hg revert --all -v
35 echo %% should say no changes needed
35 echo %% should say no changes needed
36 hg revert a
36 hg revert a
37 echo %% should say file not managed
37 echo %% should say file not managed
38 echo q > q
38 echo q > q
39 hg revert q
39 hg revert q
40 rm q
40 rm q
41 echo %% should say file not found
41 echo %% should say file not found
42 hg revert notfound
42 hg revert notfound
43 hg rm a
43 hg rm a
44 hg commit -m "second" -d "1000000 0"
44 hg commit -m "second" -d "1000000 0"
45 echo z > z
45 echo z > z
46 hg add z
46 hg add z
47 hg st
47 hg st
48 echo %% should add a, forget z
48 echo %% should add a, forget z
49 hg revert --all -r0
49 hg revert --all -r0
50 echo %% should forget a
50 echo %% should forget a
51 hg revert --all -rtip
51 hg revert --all -rtip
52 rm a *.orig
52 rm a *.orig
53 echo %% should silently add a
53 echo %% should silently add a
54 hg revert -r0 a
54 hg revert -r0 a
55 hg st a
55 hg st a
56
56
57 hg update -C
57 hg update -C
58 chmod +x c
58 chmod +x c
59 hg revert --all
59 hg revert --all
60 echo %% should print non-executable
60 echo %% should print non-executable
61 test -x c || echo non-executable
61 test -x c || echo non-executable
62
62
63 chmod +x c
63 chmod +x c
64 hg commit -d '1000001 0' -m exe
64 hg commit -d '1000001 0' -m exe
65
65
66 chmod -x c
66 chmod -x c
67 hg revert --all
67 hg revert --all
68 echo %% should print executable
68 echo %% should print executable
69 test -x c && echo executable
69 test -x c && echo executable
70
70
71 echo %% issue 241
71 echo %% issue 241
72 hg init a
72 hg init a
73 cd a
73 cd a
74 echo a >> a
74 echo a >> a
75 hg commit -A -d '1 0' -m a
75 hg commit -A -d '1 0' -m a
76 echo a >> a
76 echo a >> a
77 hg commit -d '2 0' -m a
77 hg commit -d '2 0' -m a
78 hg update 0
78 hg update 0
79 mkdir b
79 mkdir b
80 echo b > b/b
80 echo b > b/b
81
81
82 echo % should fail - no arguments
82 echo % should fail - no arguments
83 hg revert -rtip
83 hg revert -rtip
84
84
85 echo % should succeed
85 echo % should succeed
86 hg revert --all -rtip
86 hg revert --all -rtip
87
87
88 echo %% issue332
88 echo %% issue332
89 hg ci -A -m b -d '1000001 0'
89 hg ci -A -m b -d '1000001 0'
90 echo foobar > b/b
90 echo foobar > b/b
91 hg revert b
91 hg revert b
92 echo foobar > b/b
93 hg revert .
92 true
94 true
@@ -1,63 +1,64
1 %% should show b unknown
1 %% should show b unknown
2 ? b
2 ? b
3 %% should show b unknown and c modified
3 %% should show b unknown and c modified
4 M c
4 M c
5 ? b
5 ? b
6 %% should show b added and c modified
6 %% should show b added and c modified
7 M c
7 M c
8 A b
8 A b
9 %% should show a removed, b added and c modified
9 %% should show a removed, b added and c modified
10 M c
10 M c
11 A b
11 A b
12 R a
12 R a
13 %% should show b added, copy saved, and c modified
13 %% should show b added, copy saved, and c modified
14 M c
14 M c
15 A b
15 A b
16 %% should show b unknown, and c modified
16 %% should show b unknown, and c modified
17 M c
17 M c
18 ? b
18 ? b
19 %% should show unknown: b
19 %% should show unknown: b
20 ? b
20 ? b
21 %% should show a b c e
21 %% should show a b c e
22 a
22 a
23 b
23 b
24 c
24 c
25 e
25 e
26 %% should verbosely save backup to e.orig
26 %% should verbosely save backup to e.orig
27 saving current version of e as e.orig
27 saving current version of e as e.orig
28 reverting e
28 reverting e
29 resolving manifests
29 resolving manifests
30 getting e
30 getting e
31 %% should say no changes needed
31 %% should say no changes needed
32 no changes needed to a
32 no changes needed to a
33 %% should say file not managed
33 %% should say file not managed
34 file not managed: q
34 file not managed: q
35 %% should say file not found
35 %% should say file not found
36 notfound: No such file in rev 095eacd0c0d7
36 notfound: No such file in rev 095eacd0c0d7
37 A z
37 A z
38 ? b
38 ? b
39 ? e.orig
39 ? e.orig
40 %% should add a, forget z
40 %% should add a, forget z
41 adding a
41 adding a
42 forgetting z
42 forgetting z
43 %% should forget a
43 %% should forget a
44 forgetting a
44 forgetting a
45 %% should silently add a
45 %% should silently add a
46 A a
46 A a
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
48 reverting c
48 reverting c
49 %% should print non-executable
49 %% should print non-executable
50 non-executable
50 non-executable
51 reverting c
51 reverting c
52 %% should print executable
52 %% should print executable
53 executable
53 executable
54 %% issue 241
54 %% issue 241
55 adding a
55 adding a
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 % should fail - no arguments
57 % should fail - no arguments
58 abort: no files or directories specified; use --all to revert the whole repo
58 abort: no files or directories specified; use --all to revert the whole repo
59 % should succeed
59 % should succeed
60 reverting a
60 reverting a
61 %% issue332
61 %% issue332
62 adding b/b
62 adding b/b
63 reverting b/b
63 reverting b/b
64 reverting b/b
General Comments 0
You need to be logged in to leave comments. Login now