##// END OF EJS Templates
http: deliver hook output to client
Maxim Khitrov -
r11469:c37f35d7 stable
parent child Browse files
Show More
@@ -1,147 +1,150 b''
1 # hook.py - hook support for mercurial
1 # hook.py - hook support for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 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 i18n import _
8 from i18n import _
9 import os, sys
9 import os, sys
10 import extensions, util
10 import extensions, util
11
11
12 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
12 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
13 '''call python hook. hook is callable object, looked up as
13 '''call python hook. hook is callable object, looked up as
14 name in python module. if callable returns "true", hook
14 name in python module. if callable returns "true", hook
15 fails, else passes. if hook raises exception, treated as
15 fails, else passes. if hook raises exception, treated as
16 hook failure. exception propagates if throw is "true".
16 hook failure. exception propagates if throw is "true".
17
17
18 reason for "true" meaning "hook failed" is so that
18 reason for "true" meaning "hook failed" is so that
19 unmodified commands (e.g. mercurial.commands.update) can
19 unmodified commands (e.g. mercurial.commands.update) can
20 be run as hooks without wrappers to convert return values.'''
20 be run as hooks without wrappers to convert return values.'''
21
21
22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
23 obj = funcname
23 obj = funcname
24 if not hasattr(obj, '__call__'):
24 if not hasattr(obj, '__call__'):
25 d = funcname.rfind('.')
25 d = funcname.rfind('.')
26 if d == -1:
26 if d == -1:
27 raise util.Abort(_('%s hook is invalid ("%s" not in '
27 raise util.Abort(_('%s hook is invalid ("%s" not in '
28 'a module)') % (hname, funcname))
28 'a module)') % (hname, funcname))
29 modname = funcname[:d]
29 modname = funcname[:d]
30 oldpaths = sys.path
30 oldpaths = sys.path
31 if hasattr(sys, "frozen"):
31 if hasattr(sys, "frozen"):
32 # binary installs require sys.path manipulation
32 # binary installs require sys.path manipulation
33 modpath, modfile = os.path.split(modname)
33 modpath, modfile = os.path.split(modname)
34 if modpath and modfile:
34 if modpath and modfile:
35 sys.path = sys.path[:] + [modpath]
35 sys.path = sys.path[:] + [modpath]
36 modname = modfile
36 modname = modfile
37 try:
37 try:
38 obj = __import__(modname)
38 obj = __import__(modname)
39 except ImportError:
39 except ImportError:
40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
41 try:
41 try:
42 # extensions are loaded with hgext_ prefix
42 # extensions are loaded with hgext_ prefix
43 obj = __import__("hgext_%s" % modname)
43 obj = __import__("hgext_%s" % modname)
44 except ImportError:
44 except ImportError:
45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
46 if ui.tracebackflag:
46 if ui.tracebackflag:
47 ui.warn(_('exception from first failed import attempt:\n'))
47 ui.warn(_('exception from first failed import attempt:\n'))
48 ui.traceback(e1)
48 ui.traceback(e1)
49 if ui.tracebackflag:
49 if ui.tracebackflag:
50 ui.warn(_('exception from second failed import attempt:\n'))
50 ui.warn(_('exception from second failed import attempt:\n'))
51 ui.traceback(e2)
51 ui.traceback(e2)
52 raise util.Abort(_('%s hook is invalid '
52 raise util.Abort(_('%s hook is invalid '
53 '(import of "%s" failed)') %
53 '(import of "%s" failed)') %
54 (hname, modname))
54 (hname, modname))
55 sys.path = oldpaths
55 sys.path = oldpaths
56 try:
56 try:
57 for p in funcname.split('.')[1:]:
57 for p in funcname.split('.')[1:]:
58 obj = getattr(obj, p)
58 obj = getattr(obj, p)
59 except AttributeError:
59 except AttributeError:
60 raise util.Abort(_('%s hook is invalid '
60 raise util.Abort(_('%s hook is invalid '
61 '("%s" is not defined)') %
61 '("%s" is not defined)') %
62 (hname, funcname))
62 (hname, funcname))
63 if not hasattr(obj, '__call__'):
63 if not hasattr(obj, '__call__'):
64 raise util.Abort(_('%s hook is invalid '
64 raise util.Abort(_('%s hook is invalid '
65 '("%s" is not callable)') %
65 '("%s" is not callable)') %
66 (hname, funcname))
66 (hname, funcname))
67 try:
67 try:
68 r = obj(ui=ui, repo=repo, hooktype=name, **args)
68 r = obj(ui=ui, repo=repo, hooktype=name, **args)
69 except KeyboardInterrupt:
69 except KeyboardInterrupt:
70 raise
70 raise
71 except Exception, exc:
71 except Exception, exc:
72 if isinstance(exc, util.Abort):
72 if isinstance(exc, util.Abort):
73 ui.warn(_('error: %s hook failed: %s\n') %
73 ui.warn(_('error: %s hook failed: %s\n') %
74 (hname, exc.args[0]))
74 (hname, exc.args[0]))
75 else:
75 else:
76 ui.warn(_('error: %s hook raised an exception: '
76 ui.warn(_('error: %s hook raised an exception: '
77 '%s\n') % (hname, exc))
77 '%s\n') % (hname, exc))
78 if throw:
78 if throw:
79 raise
79 raise
80 ui.traceback()
80 ui.traceback()
81 return True
81 return True
82 if r:
82 if r:
83 if throw:
83 if throw:
84 raise util.Abort(_('%s hook failed') % hname)
84 raise util.Abort(_('%s hook failed') % hname)
85 ui.warn(_('warning: %s hook failed\n') % hname)
85 ui.warn(_('warning: %s hook failed\n') % hname)
86 return r
86 return r
87
87
88 def _exthook(ui, repo, name, cmd, args, throw):
88 def _exthook(ui, repo, name, cmd, args, throw):
89 ui.note(_("running hook %s: %s\n") % (name, cmd))
89 ui.note(_("running hook %s: %s\n") % (name, cmd))
90
90
91 env = {}
91 env = {}
92 for k, v in args.iteritems():
92 for k, v in args.iteritems():
93 if hasattr(v, '__call__'):
93 if hasattr(v, '__call__'):
94 v = v()
94 v = v()
95 env['HG_' + k.upper()] = v
95 env['HG_' + k.upper()] = v
96
96
97 if repo:
97 if repo:
98 cwd = repo.root
98 cwd = repo.root
99 else:
99 else:
100 cwd = os.getcwd()
100 cwd = os.getcwd()
101 r = util.system(cmd, environ=env, cwd=cwd)
101 if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'):
102 r = util.system(cmd, environ=env, cwd=cwd, out=ui)
103 else:
104 r = util.system(cmd, environ=env, cwd=cwd)
102 if r:
105 if r:
103 desc, r = util.explain_exit(r)
106 desc, r = util.explain_exit(r)
104 if throw:
107 if throw:
105 raise util.Abort(_('%s hook %s') % (name, desc))
108 raise util.Abort(_('%s hook %s') % (name, desc))
106 ui.warn(_('warning: %s hook %s\n') % (name, desc))
109 ui.warn(_('warning: %s hook %s\n') % (name, desc))
107 return r
110 return r
108
111
109 _redirect = False
112 _redirect = False
110 def redirect(state):
113 def redirect(state):
111 global _redirect
114 global _redirect
112 _redirect = state
115 _redirect = state
113
116
114 def hook(ui, repo, name, throw=False, **args):
117 def hook(ui, repo, name, throw=False, **args):
115 r = False
118 r = False
116
119
117 oldstdout = -1
120 oldstdout = -1
118 if _redirect:
121 if _redirect:
119 stdoutno = sys.__stdout__.fileno()
122 stdoutno = sys.__stdout__.fileno()
120 stderrno = sys.__stderr__.fileno()
123 stderrno = sys.__stderr__.fileno()
121 # temporarily redirect stdout to stderr, if possible
124 # temporarily redirect stdout to stderr, if possible
122 if stdoutno >= 0 and stderrno >= 0:
125 if stdoutno >= 0 and stderrno >= 0:
123 oldstdout = os.dup(stdoutno)
126 oldstdout = os.dup(stdoutno)
124 os.dup2(stderrno, stdoutno)
127 os.dup2(stderrno, stdoutno)
125
128
126 try:
129 try:
127 for hname, cmd in ui.configitems('hooks'):
130 for hname, cmd in ui.configitems('hooks'):
128 if hname.split('.')[0] != name or not cmd:
131 if hname.split('.')[0] != name or not cmd:
129 continue
132 continue
130 if hasattr(cmd, '__call__'):
133 if hasattr(cmd, '__call__'):
131 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
134 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
132 elif cmd.startswith('python:'):
135 elif cmd.startswith('python:'):
133 if cmd.count(':') >= 2:
136 if cmd.count(':') >= 2:
134 path, cmd = cmd[7:].rsplit(':', 1)
137 path, cmd = cmd[7:].rsplit(':', 1)
135 mod = extensions.loadpath(path, 'hghook.%s' % hname)
138 mod = extensions.loadpath(path, 'hghook.%s' % hname)
136 hookfn = getattr(mod, cmd)
139 hookfn = getattr(mod, cmd)
137 else:
140 else:
138 hookfn = cmd[7:].strip()
141 hookfn = cmd[7:].strip()
139 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
142 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
140 else:
143 else:
141 r = _exthook(ui, repo, hname, cmd, args, throw) or r
144 r = _exthook(ui, repo, hname, cmd, args, throw) or r
142 finally:
145 finally:
143 if _redirect and oldstdout >= 0:
146 if _redirect and oldstdout >= 0:
144 os.dup2(oldstdout, stdoutno)
147 os.dup2(oldstdout, stdoutno)
145 os.close(oldstdout)
148 os.close(oldstdout)
146
149
147 return r
150 return r
@@ -1,1378 +1,1390 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 cStringIO, errno, re, shutil, sys, tempfile, traceback
18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, textwrap, unicodedata, signal
19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 import imp
20 import imp
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 try:
31 try:
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 except ImportError:
33 except ImportError:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 def fakebuffer(sliceable, offset=0):
41 def fakebuffer(sliceable, offset=0):
42 return sliceable[offset:]
42 return sliceable[offset:]
43 if not hasattr(__builtin__, 'buffer'):
43 if not hasattr(__builtin__, 'buffer'):
44 __builtin__.buffer = fakebuffer
44 __builtin__.buffer = fakebuffer
45
45
46 import subprocess
46 import subprocess
47 closefds = os.name == 'posix'
47 closefds = os.name == 'posix'
48
48
49 def popen2(cmd, env=None, newlines=False):
49 def popen2(cmd, env=None, newlines=False):
50 # Setting bufsize to -1 lets the system decide the buffer size.
50 # Setting bufsize to -1 lets the system decide the buffer size.
51 # The default for bufsize is 0, meaning unbuffered. This leads to
51 # The default for bufsize is 0, meaning unbuffered. This leads to
52 # poor performance on Mac OS X: http://bugs.python.org/issue4194
52 # poor performance on Mac OS X: http://bugs.python.org/issue4194
53 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
53 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
54 close_fds=closefds,
54 close_fds=closefds,
55 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
55 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
56 universal_newlines=newlines,
56 universal_newlines=newlines,
57 env=env)
57 env=env)
58 return p.stdin, p.stdout
58 return p.stdin, p.stdout
59
59
60 def popen3(cmd, env=None, newlines=False):
60 def popen3(cmd, env=None, newlines=False):
61 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
61 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
62 close_fds=closefds,
62 close_fds=closefds,
63 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
63 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
64 stderr=subprocess.PIPE,
64 stderr=subprocess.PIPE,
65 universal_newlines=newlines,
65 universal_newlines=newlines,
66 env=env)
66 env=env)
67 return p.stdin, p.stdout, p.stderr
67 return p.stdin, p.stdout, p.stderr
68
68
69 def version():
69 def version():
70 """Return version information if available."""
70 """Return version information if available."""
71 try:
71 try:
72 import __version__
72 import __version__
73 return __version__.version
73 return __version__.version
74 except ImportError:
74 except ImportError:
75 return 'unknown'
75 return 'unknown'
76
76
77 # used by parsedate
77 # used by parsedate
78 defaultdateformats = (
78 defaultdateformats = (
79 '%Y-%m-%d %H:%M:%S',
79 '%Y-%m-%d %H:%M:%S',
80 '%Y-%m-%d %I:%M:%S%p',
80 '%Y-%m-%d %I:%M:%S%p',
81 '%Y-%m-%d %H:%M',
81 '%Y-%m-%d %H:%M',
82 '%Y-%m-%d %I:%M%p',
82 '%Y-%m-%d %I:%M%p',
83 '%Y-%m-%d',
83 '%Y-%m-%d',
84 '%m-%d',
84 '%m-%d',
85 '%m/%d',
85 '%m/%d',
86 '%m/%d/%y',
86 '%m/%d/%y',
87 '%m/%d/%Y',
87 '%m/%d/%Y',
88 '%a %b %d %H:%M:%S %Y',
88 '%a %b %d %H:%M:%S %Y',
89 '%a %b %d %I:%M:%S%p %Y',
89 '%a %b %d %I:%M:%S%p %Y',
90 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
90 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
91 '%b %d %H:%M:%S %Y',
91 '%b %d %H:%M:%S %Y',
92 '%b %d %I:%M:%S%p %Y',
92 '%b %d %I:%M:%S%p %Y',
93 '%b %d %H:%M:%S',
93 '%b %d %H:%M:%S',
94 '%b %d %I:%M:%S%p',
94 '%b %d %I:%M:%S%p',
95 '%b %d %H:%M',
95 '%b %d %H:%M',
96 '%b %d %I:%M%p',
96 '%b %d %I:%M%p',
97 '%b %d %Y',
97 '%b %d %Y',
98 '%b %d',
98 '%b %d',
99 '%H:%M:%S',
99 '%H:%M:%S',
100 '%I:%M:%S%p',
100 '%I:%M:%S%p',
101 '%H:%M',
101 '%H:%M',
102 '%I:%M%p',
102 '%I:%M%p',
103 )
103 )
104
104
105 extendeddateformats = defaultdateformats + (
105 extendeddateformats = defaultdateformats + (
106 "%Y",
106 "%Y",
107 "%Y-%m",
107 "%Y-%m",
108 "%b",
108 "%b",
109 "%b %Y",
109 "%b %Y",
110 )
110 )
111
111
112 def cachefunc(func):
112 def cachefunc(func):
113 '''cache the result of function calls'''
113 '''cache the result of function calls'''
114 # XXX doesn't handle keywords args
114 # XXX doesn't handle keywords args
115 cache = {}
115 cache = {}
116 if func.func_code.co_argcount == 1:
116 if func.func_code.co_argcount == 1:
117 # we gain a small amount of time because
117 # we gain a small amount of time because
118 # we don't need to pack/unpack the list
118 # we don't need to pack/unpack the list
119 def f(arg):
119 def f(arg):
120 if arg not in cache:
120 if arg not in cache:
121 cache[arg] = func(arg)
121 cache[arg] = func(arg)
122 return cache[arg]
122 return cache[arg]
123 else:
123 else:
124 def f(*args):
124 def f(*args):
125 if args not in cache:
125 if args not in cache:
126 cache[args] = func(*args)
126 cache[args] = func(*args)
127 return cache[args]
127 return cache[args]
128
128
129 return f
129 return f
130
130
131 def lrucachefunc(func):
131 def lrucachefunc(func):
132 '''cache most recent results of function calls'''
132 '''cache most recent results of function calls'''
133 cache = {}
133 cache = {}
134 order = []
134 order = []
135 if func.func_code.co_argcount == 1:
135 if func.func_code.co_argcount == 1:
136 def f(arg):
136 def f(arg):
137 if arg not in cache:
137 if arg not in cache:
138 if len(cache) > 20:
138 if len(cache) > 20:
139 del cache[order.pop(0)]
139 del cache[order.pop(0)]
140 cache[arg] = func(arg)
140 cache[arg] = func(arg)
141 else:
141 else:
142 order.remove(arg)
142 order.remove(arg)
143 order.append(arg)
143 order.append(arg)
144 return cache[arg]
144 return cache[arg]
145 else:
145 else:
146 def f(*args):
146 def f(*args):
147 if args not in cache:
147 if args not in cache:
148 if len(cache) > 20:
148 if len(cache) > 20:
149 del cache[order.pop(0)]
149 del cache[order.pop(0)]
150 cache[args] = func(*args)
150 cache[args] = func(*args)
151 else:
151 else:
152 order.remove(args)
152 order.remove(args)
153 order.append(args)
153 order.append(args)
154 return cache[args]
154 return cache[args]
155
155
156 return f
156 return f
157
157
158 class propertycache(object):
158 class propertycache(object):
159 def __init__(self, func):
159 def __init__(self, func):
160 self.func = func
160 self.func = func
161 self.name = func.__name__
161 self.name = func.__name__
162 def __get__(self, obj, type=None):
162 def __get__(self, obj, type=None):
163 result = self.func(obj)
163 result = self.func(obj)
164 setattr(obj, self.name, result)
164 setattr(obj, self.name, result)
165 return result
165 return result
166
166
167 def pipefilter(s, cmd):
167 def pipefilter(s, cmd):
168 '''filter string S through command CMD, returning its output'''
168 '''filter string S through command CMD, returning its output'''
169 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
169 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
170 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
170 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
171 pout, perr = p.communicate(s)
171 pout, perr = p.communicate(s)
172 return pout
172 return pout
173
173
174 def tempfilter(s, cmd):
174 def tempfilter(s, cmd):
175 '''filter string S through a pair of temporary files with CMD.
175 '''filter string S through a pair of temporary files with CMD.
176 CMD is used as a template to create the real command to be run,
176 CMD is used as a template to create the real command to be run,
177 with the strings INFILE and OUTFILE replaced by the real names of
177 with the strings INFILE and OUTFILE replaced by the real names of
178 the temporary files generated.'''
178 the temporary files generated.'''
179 inname, outname = None, None
179 inname, outname = None, None
180 try:
180 try:
181 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
181 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
182 fp = os.fdopen(infd, 'wb')
182 fp = os.fdopen(infd, 'wb')
183 fp.write(s)
183 fp.write(s)
184 fp.close()
184 fp.close()
185 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
185 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
186 os.close(outfd)
186 os.close(outfd)
187 cmd = cmd.replace('INFILE', inname)
187 cmd = cmd.replace('INFILE', inname)
188 cmd = cmd.replace('OUTFILE', outname)
188 cmd = cmd.replace('OUTFILE', outname)
189 code = os.system(cmd)
189 code = os.system(cmd)
190 if sys.platform == 'OpenVMS' and code & 1:
190 if sys.platform == 'OpenVMS' and code & 1:
191 code = 0
191 code = 0
192 if code:
192 if code:
193 raise Abort(_("command '%s' failed: %s") %
193 raise Abort(_("command '%s' failed: %s") %
194 (cmd, explain_exit(code)))
194 (cmd, explain_exit(code)))
195 return open(outname, 'rb').read()
195 return open(outname, 'rb').read()
196 finally:
196 finally:
197 try:
197 try:
198 if inname:
198 if inname:
199 os.unlink(inname)
199 os.unlink(inname)
200 except:
200 except:
201 pass
201 pass
202 try:
202 try:
203 if outname:
203 if outname:
204 os.unlink(outname)
204 os.unlink(outname)
205 except:
205 except:
206 pass
206 pass
207
207
208 filtertable = {
208 filtertable = {
209 'tempfile:': tempfilter,
209 'tempfile:': tempfilter,
210 'pipe:': pipefilter,
210 'pipe:': pipefilter,
211 }
211 }
212
212
213 def filter(s, cmd):
213 def filter(s, cmd):
214 "filter a string through a command that transforms its input to its output"
214 "filter a string through a command that transforms its input to its output"
215 for name, fn in filtertable.iteritems():
215 for name, fn in filtertable.iteritems():
216 if cmd.startswith(name):
216 if cmd.startswith(name):
217 return fn(s, cmd[len(name):].lstrip())
217 return fn(s, cmd[len(name):].lstrip())
218 return pipefilter(s, cmd)
218 return pipefilter(s, cmd)
219
219
220 def binary(s):
220 def binary(s):
221 """return true if a string is binary data"""
221 """return true if a string is binary data"""
222 return bool(s and '\0' in s)
222 return bool(s and '\0' in s)
223
223
224 def increasingchunks(source, min=1024, max=65536):
224 def increasingchunks(source, min=1024, max=65536):
225 '''return no less than min bytes per chunk while data remains,
225 '''return no less than min bytes per chunk while data remains,
226 doubling min after each chunk until it reaches max'''
226 doubling min after each chunk until it reaches max'''
227 def log2(x):
227 def log2(x):
228 if not x:
228 if not x:
229 return 0
229 return 0
230 i = 0
230 i = 0
231 while x:
231 while x:
232 x >>= 1
232 x >>= 1
233 i += 1
233 i += 1
234 return i - 1
234 return i - 1
235
235
236 buf = []
236 buf = []
237 blen = 0
237 blen = 0
238 for chunk in source:
238 for chunk in source:
239 buf.append(chunk)
239 buf.append(chunk)
240 blen += len(chunk)
240 blen += len(chunk)
241 if blen >= min:
241 if blen >= min:
242 if min < max:
242 if min < max:
243 min = min << 1
243 min = min << 1
244 nmin = 1 << log2(blen)
244 nmin = 1 << log2(blen)
245 if nmin > min:
245 if nmin > min:
246 min = nmin
246 min = nmin
247 if min > max:
247 if min > max:
248 min = max
248 min = max
249 yield ''.join(buf)
249 yield ''.join(buf)
250 blen = 0
250 blen = 0
251 buf = []
251 buf = []
252 if buf:
252 if buf:
253 yield ''.join(buf)
253 yield ''.join(buf)
254
254
255 Abort = error.Abort
255 Abort = error.Abort
256
256
257 def always(fn):
257 def always(fn):
258 return True
258 return True
259
259
260 def never(fn):
260 def never(fn):
261 return False
261 return False
262
262
263 def pathto(root, n1, n2):
263 def pathto(root, n1, n2):
264 '''return the relative path from one place to another.
264 '''return the relative path from one place to another.
265 root should use os.sep to separate directories
265 root should use os.sep to separate directories
266 n1 should use os.sep to separate directories
266 n1 should use os.sep to separate directories
267 n2 should use "/" to separate directories
267 n2 should use "/" to separate directories
268 returns an os.sep-separated path.
268 returns an os.sep-separated path.
269
269
270 If n1 is a relative path, it's assumed it's
270 If n1 is a relative path, it's assumed it's
271 relative to root.
271 relative to root.
272 n2 should always be relative to root.
272 n2 should always be relative to root.
273 '''
273 '''
274 if not n1:
274 if not n1:
275 return localpath(n2)
275 return localpath(n2)
276 if os.path.isabs(n1):
276 if os.path.isabs(n1):
277 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
277 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
278 return os.path.join(root, localpath(n2))
278 return os.path.join(root, localpath(n2))
279 n2 = '/'.join((pconvert(root), n2))
279 n2 = '/'.join((pconvert(root), n2))
280 a, b = splitpath(n1), n2.split('/')
280 a, b = splitpath(n1), n2.split('/')
281 a.reverse()
281 a.reverse()
282 b.reverse()
282 b.reverse()
283 while a and b and a[-1] == b[-1]:
283 while a and b and a[-1] == b[-1]:
284 a.pop()
284 a.pop()
285 b.pop()
285 b.pop()
286 b.reverse()
286 b.reverse()
287 return os.sep.join((['..'] * len(a)) + b) or '.'
287 return os.sep.join((['..'] * len(a)) + b) or '.'
288
288
289 def canonpath(root, cwd, myname):
289 def canonpath(root, cwd, myname):
290 """return the canonical path of myname, given cwd and root"""
290 """return the canonical path of myname, given cwd and root"""
291 if endswithsep(root):
291 if endswithsep(root):
292 rootsep = root
292 rootsep = root
293 else:
293 else:
294 rootsep = root + os.sep
294 rootsep = root + os.sep
295 name = myname
295 name = myname
296 if not os.path.isabs(name):
296 if not os.path.isabs(name):
297 name = os.path.join(root, cwd, name)
297 name = os.path.join(root, cwd, name)
298 name = os.path.normpath(name)
298 name = os.path.normpath(name)
299 audit_path = path_auditor(root)
299 audit_path = path_auditor(root)
300 if name != rootsep and name.startswith(rootsep):
300 if name != rootsep and name.startswith(rootsep):
301 name = name[len(rootsep):]
301 name = name[len(rootsep):]
302 audit_path(name)
302 audit_path(name)
303 return pconvert(name)
303 return pconvert(name)
304 elif name == root:
304 elif name == root:
305 return ''
305 return ''
306 else:
306 else:
307 # Determine whether `name' is in the hierarchy at or beneath `root',
307 # Determine whether `name' is in the hierarchy at or beneath `root',
308 # by iterating name=dirname(name) until that causes no change (can't
308 # by iterating name=dirname(name) until that causes no change (can't
309 # check name == '/', because that doesn't work on windows). For each
309 # check name == '/', because that doesn't work on windows). For each
310 # `name', compare dev/inode numbers. If they match, the list `rel'
310 # `name', compare dev/inode numbers. If they match, the list `rel'
311 # holds the reversed list of components making up the relative file
311 # holds the reversed list of components making up the relative file
312 # name we want.
312 # name we want.
313 root_st = os.stat(root)
313 root_st = os.stat(root)
314 rel = []
314 rel = []
315 while True:
315 while True:
316 try:
316 try:
317 name_st = os.stat(name)
317 name_st = os.stat(name)
318 except OSError:
318 except OSError:
319 break
319 break
320 if samestat(name_st, root_st):
320 if samestat(name_st, root_st):
321 if not rel:
321 if not rel:
322 # name was actually the same as root (maybe a symlink)
322 # name was actually the same as root (maybe a symlink)
323 return ''
323 return ''
324 rel.reverse()
324 rel.reverse()
325 name = os.path.join(*rel)
325 name = os.path.join(*rel)
326 audit_path(name)
326 audit_path(name)
327 return pconvert(name)
327 return pconvert(name)
328 dirname, basename = os.path.split(name)
328 dirname, basename = os.path.split(name)
329 rel.append(basename)
329 rel.append(basename)
330 if dirname == name:
330 if dirname == name:
331 break
331 break
332 name = dirname
332 name = dirname
333
333
334 raise Abort('%s not under root' % myname)
334 raise Abort('%s not under root' % myname)
335
335
336 _hgexecutable = None
336 _hgexecutable = None
337
337
338 def main_is_frozen():
338 def main_is_frozen():
339 """return True if we are a frozen executable.
339 """return True if we are a frozen executable.
340
340
341 The code supports py2exe (most common, Windows only) and tools/freeze
341 The code supports py2exe (most common, Windows only) and tools/freeze
342 (portable, not much used).
342 (portable, not much used).
343 """
343 """
344 return (hasattr(sys, "frozen") or # new py2exe
344 return (hasattr(sys, "frozen") or # new py2exe
345 hasattr(sys, "importers") or # old py2exe
345 hasattr(sys, "importers") or # old py2exe
346 imp.is_frozen("__main__")) # tools/freeze
346 imp.is_frozen("__main__")) # tools/freeze
347
347
348 def hgexecutable():
348 def hgexecutable():
349 """return location of the 'hg' executable.
349 """return location of the 'hg' executable.
350
350
351 Defaults to $HG or 'hg' in the search path.
351 Defaults to $HG or 'hg' in the search path.
352 """
352 """
353 if _hgexecutable is None:
353 if _hgexecutable is None:
354 hg = os.environ.get('HG')
354 hg = os.environ.get('HG')
355 if hg:
355 if hg:
356 set_hgexecutable(hg)
356 set_hgexecutable(hg)
357 elif main_is_frozen():
357 elif main_is_frozen():
358 set_hgexecutable(sys.executable)
358 set_hgexecutable(sys.executable)
359 else:
359 else:
360 exe = find_exe('hg') or os.path.basename(sys.argv[0])
360 exe = find_exe('hg') or os.path.basename(sys.argv[0])
361 set_hgexecutable(exe)
361 set_hgexecutable(exe)
362 return _hgexecutable
362 return _hgexecutable
363
363
364 def set_hgexecutable(path):
364 def set_hgexecutable(path):
365 """set location of the 'hg' executable"""
365 """set location of the 'hg' executable"""
366 global _hgexecutable
366 global _hgexecutable
367 _hgexecutable = path
367 _hgexecutable = path
368
368
369 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
369 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
370 '''enhanced shell command execution.
370 '''enhanced shell command execution.
371 run with environment maybe modified, maybe in different dir.
371 run with environment maybe modified, maybe in different dir.
372
372
373 if command fails and onerr is None, return status. if ui object,
373 if command fails and onerr is None, return status. if ui object,
374 print error message and return status, else raise onerr object as
374 print error message and return status, else raise onerr object as
375 exception.'''
375 exception.
376
377 if out is specified, it is assumed to be a file-like object that has a
378 write() method. stdout and stderr will be redirected to out.'''
376 def py2shell(val):
379 def py2shell(val):
377 'convert python object into string that is useful to shell'
380 'convert python object into string that is useful to shell'
378 if val is None or val is False:
381 if val is None or val is False:
379 return '0'
382 return '0'
380 if val is True:
383 if val is True:
381 return '1'
384 return '1'
382 return str(val)
385 return str(val)
383 origcmd = cmd
386 origcmd = cmd
384 if os.name == 'nt':
387 if os.name == 'nt':
385 cmd = '"%s"' % cmd
388 cmd = '"%s"' % cmd
386 env = dict(os.environ)
389 env = dict(os.environ)
387 env.update((k, py2shell(v)) for k, v in environ.iteritems())
390 env.update((k, py2shell(v)) for k, v in environ.iteritems())
388 env['HG'] = hgexecutable()
391 env['HG'] = hgexecutable()
389 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
392 if out is None:
390 env=env, cwd=cwd)
393 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
394 env=env, cwd=cwd)
395 else:
396 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
397 env=env, cwd=cwd, stdout=subprocess.PIPE,
398 stderr=subprocess.STDOUT)
399 for line in proc.stdout:
400 out.write(line)
401 proc.wait()
402 rc = proc.returncode
391 if sys.platform == 'OpenVMS' and rc & 1:
403 if sys.platform == 'OpenVMS' and rc & 1:
392 rc = 0
404 rc = 0
393 if rc and onerr:
405 if rc and onerr:
394 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
406 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
395 explain_exit(rc)[0])
407 explain_exit(rc)[0])
396 if errprefix:
408 if errprefix:
397 errmsg = '%s: %s' % (errprefix, errmsg)
409 errmsg = '%s: %s' % (errprefix, errmsg)
398 try:
410 try:
399 onerr.warn(errmsg + '\n')
411 onerr.warn(errmsg + '\n')
400 except AttributeError:
412 except AttributeError:
401 raise onerr(errmsg)
413 raise onerr(errmsg)
402 return rc
414 return rc
403
415
404 def checksignature(func):
416 def checksignature(func):
405 '''wrap a function with code to check for calling errors'''
417 '''wrap a function with code to check for calling errors'''
406 def check(*args, **kwargs):
418 def check(*args, **kwargs):
407 try:
419 try:
408 return func(*args, **kwargs)
420 return func(*args, **kwargs)
409 except TypeError:
421 except TypeError:
410 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
422 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
411 raise error.SignatureError
423 raise error.SignatureError
412 raise
424 raise
413
425
414 return check
426 return check
415
427
416 # os.path.lexists is not available on python2.3
428 # os.path.lexists is not available on python2.3
417 def lexists(filename):
429 def lexists(filename):
418 "test whether a file with this name exists. does not follow symlinks"
430 "test whether a file with this name exists. does not follow symlinks"
419 try:
431 try:
420 os.lstat(filename)
432 os.lstat(filename)
421 except:
433 except:
422 return False
434 return False
423 return True
435 return True
424
436
425 def unlink(f):
437 def unlink(f):
426 """unlink and remove the directory if it is empty"""
438 """unlink and remove the directory if it is empty"""
427 os.unlink(f)
439 os.unlink(f)
428 # try removing directories that might now be empty
440 # try removing directories that might now be empty
429 try:
441 try:
430 os.removedirs(os.path.dirname(f))
442 os.removedirs(os.path.dirname(f))
431 except OSError:
443 except OSError:
432 pass
444 pass
433
445
434 def copyfile(src, dest):
446 def copyfile(src, dest):
435 "copy a file, preserving mode and atime/mtime"
447 "copy a file, preserving mode and atime/mtime"
436 if os.path.islink(src):
448 if os.path.islink(src):
437 try:
449 try:
438 os.unlink(dest)
450 os.unlink(dest)
439 except:
451 except:
440 pass
452 pass
441 os.symlink(os.readlink(src), dest)
453 os.symlink(os.readlink(src), dest)
442 else:
454 else:
443 try:
455 try:
444 shutil.copyfile(src, dest)
456 shutil.copyfile(src, dest)
445 shutil.copystat(src, dest)
457 shutil.copystat(src, dest)
446 except shutil.Error, inst:
458 except shutil.Error, inst:
447 raise Abort(str(inst))
459 raise Abort(str(inst))
448
460
449 def copyfiles(src, dst, hardlink=None):
461 def copyfiles(src, dst, hardlink=None):
450 """Copy a directory tree using hardlinks if possible"""
462 """Copy a directory tree using hardlinks if possible"""
451
463
452 if hardlink is None:
464 if hardlink is None:
453 hardlink = (os.stat(src).st_dev ==
465 hardlink = (os.stat(src).st_dev ==
454 os.stat(os.path.dirname(dst)).st_dev)
466 os.stat(os.path.dirname(dst)).st_dev)
455
467
456 num = 0
468 num = 0
457 if os.path.isdir(src):
469 if os.path.isdir(src):
458 os.mkdir(dst)
470 os.mkdir(dst)
459 for name, kind in osutil.listdir(src):
471 for name, kind in osutil.listdir(src):
460 srcname = os.path.join(src, name)
472 srcname = os.path.join(src, name)
461 dstname = os.path.join(dst, name)
473 dstname = os.path.join(dst, name)
462 hardlink, n = copyfiles(srcname, dstname, hardlink)
474 hardlink, n = copyfiles(srcname, dstname, hardlink)
463 num += n
475 num += n
464 else:
476 else:
465 if hardlink:
477 if hardlink:
466 try:
478 try:
467 os_link(src, dst)
479 os_link(src, dst)
468 except (IOError, OSError):
480 except (IOError, OSError):
469 hardlink = False
481 hardlink = False
470 shutil.copy(src, dst)
482 shutil.copy(src, dst)
471 else:
483 else:
472 shutil.copy(src, dst)
484 shutil.copy(src, dst)
473 num += 1
485 num += 1
474
486
475 return hardlink, num
487 return hardlink, num
476
488
477 class path_auditor(object):
489 class path_auditor(object):
478 '''ensure that a filesystem path contains no banned components.
490 '''ensure that a filesystem path contains no banned components.
479 the following properties of a path are checked:
491 the following properties of a path are checked:
480
492
481 - under top-level .hg
493 - under top-level .hg
482 - starts at the root of a windows drive
494 - starts at the root of a windows drive
483 - contains ".."
495 - contains ".."
484 - traverses a symlink (e.g. a/symlink_here/b)
496 - traverses a symlink (e.g. a/symlink_here/b)
485 - inside a nested repository'''
497 - inside a nested repository'''
486
498
487 def __init__(self, root):
499 def __init__(self, root):
488 self.audited = set()
500 self.audited = set()
489 self.auditeddir = set()
501 self.auditeddir = set()
490 self.root = root
502 self.root = root
491
503
492 def __call__(self, path):
504 def __call__(self, path):
493 if path in self.audited:
505 if path in self.audited:
494 return
506 return
495 normpath = os.path.normcase(path)
507 normpath = os.path.normcase(path)
496 parts = splitpath(normpath)
508 parts = splitpath(normpath)
497 if (os.path.splitdrive(path)[0]
509 if (os.path.splitdrive(path)[0]
498 or parts[0].lower() in ('.hg', '.hg.', '')
510 or parts[0].lower() in ('.hg', '.hg.', '')
499 or os.pardir in parts):
511 or os.pardir in parts):
500 raise Abort(_("path contains illegal component: %s") % path)
512 raise Abort(_("path contains illegal component: %s") % path)
501 if '.hg' in path.lower():
513 if '.hg' in path.lower():
502 lparts = [p.lower() for p in parts]
514 lparts = [p.lower() for p in parts]
503 for p in '.hg', '.hg.':
515 for p in '.hg', '.hg.':
504 if p in lparts[1:]:
516 if p in lparts[1:]:
505 pos = lparts.index(p)
517 pos = lparts.index(p)
506 base = os.path.join(*parts[:pos])
518 base = os.path.join(*parts[:pos])
507 raise Abort(_('path %r is inside repo %r') % (path, base))
519 raise Abort(_('path %r is inside repo %r') % (path, base))
508 def check(prefix):
520 def check(prefix):
509 curpath = os.path.join(self.root, prefix)
521 curpath = os.path.join(self.root, prefix)
510 try:
522 try:
511 st = os.lstat(curpath)
523 st = os.lstat(curpath)
512 except OSError, err:
524 except OSError, err:
513 # EINVAL can be raised as invalid path syntax under win32.
525 # EINVAL can be raised as invalid path syntax under win32.
514 # They must be ignored for patterns can be checked too.
526 # They must be ignored for patterns can be checked too.
515 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
527 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
516 raise
528 raise
517 else:
529 else:
518 if stat.S_ISLNK(st.st_mode):
530 if stat.S_ISLNK(st.st_mode):
519 raise Abort(_('path %r traverses symbolic link %r') %
531 raise Abort(_('path %r traverses symbolic link %r') %
520 (path, prefix))
532 (path, prefix))
521 elif (stat.S_ISDIR(st.st_mode) and
533 elif (stat.S_ISDIR(st.st_mode) and
522 os.path.isdir(os.path.join(curpath, '.hg'))):
534 os.path.isdir(os.path.join(curpath, '.hg'))):
523 raise Abort(_('path %r is inside repo %r') %
535 raise Abort(_('path %r is inside repo %r') %
524 (path, prefix))
536 (path, prefix))
525 parts.pop()
537 parts.pop()
526 prefixes = []
538 prefixes = []
527 while parts:
539 while parts:
528 prefix = os.sep.join(parts)
540 prefix = os.sep.join(parts)
529 if prefix in self.auditeddir:
541 if prefix in self.auditeddir:
530 break
542 break
531 check(prefix)
543 check(prefix)
532 prefixes.append(prefix)
544 prefixes.append(prefix)
533 parts.pop()
545 parts.pop()
534
546
535 self.audited.add(path)
547 self.audited.add(path)
536 # only add prefixes to the cache after checking everything: we don't
548 # only add prefixes to the cache after checking everything: we don't
537 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
549 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
538 self.auditeddir.update(prefixes)
550 self.auditeddir.update(prefixes)
539
551
540 def nlinks(pathname):
552 def nlinks(pathname):
541 """Return number of hardlinks for the given file."""
553 """Return number of hardlinks for the given file."""
542 return os.lstat(pathname).st_nlink
554 return os.lstat(pathname).st_nlink
543
555
544 if hasattr(os, 'link'):
556 if hasattr(os, 'link'):
545 os_link = os.link
557 os_link = os.link
546 else:
558 else:
547 def os_link(src, dst):
559 def os_link(src, dst):
548 raise OSError(0, _("Hardlinks not supported"))
560 raise OSError(0, _("Hardlinks not supported"))
549
561
550 def lookup_reg(key, name=None, scope=None):
562 def lookup_reg(key, name=None, scope=None):
551 return None
563 return None
552
564
553 def hidewindow():
565 def hidewindow():
554 """Hide current shell window.
566 """Hide current shell window.
555
567
556 Used to hide the window opened when starting asynchronous
568 Used to hide the window opened when starting asynchronous
557 child process under Windows, unneeded on other systems.
569 child process under Windows, unneeded on other systems.
558 """
570 """
559 pass
571 pass
560
572
561 if os.name == 'nt':
573 if os.name == 'nt':
562 from windows import *
574 from windows import *
563 else:
575 else:
564 from posix import *
576 from posix import *
565
577
566 def makelock(info, pathname):
578 def makelock(info, pathname):
567 try:
579 try:
568 return os.symlink(info, pathname)
580 return os.symlink(info, pathname)
569 except OSError, why:
581 except OSError, why:
570 if why.errno == errno.EEXIST:
582 if why.errno == errno.EEXIST:
571 raise
583 raise
572 except AttributeError: # no symlink in os
584 except AttributeError: # no symlink in os
573 pass
585 pass
574
586
575 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
587 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
576 os.write(ld, info)
588 os.write(ld, info)
577 os.close(ld)
589 os.close(ld)
578
590
579 def readlock(pathname):
591 def readlock(pathname):
580 try:
592 try:
581 return os.readlink(pathname)
593 return os.readlink(pathname)
582 except OSError, why:
594 except OSError, why:
583 if why.errno not in (errno.EINVAL, errno.ENOSYS):
595 if why.errno not in (errno.EINVAL, errno.ENOSYS):
584 raise
596 raise
585 except AttributeError: # no symlink in os
597 except AttributeError: # no symlink in os
586 pass
598 pass
587 return posixfile(pathname).read()
599 return posixfile(pathname).read()
588
600
589 def fstat(fp):
601 def fstat(fp):
590 '''stat file object that may not have fileno method.'''
602 '''stat file object that may not have fileno method.'''
591 try:
603 try:
592 return os.fstat(fp.fileno())
604 return os.fstat(fp.fileno())
593 except AttributeError:
605 except AttributeError:
594 return os.stat(fp.name)
606 return os.stat(fp.name)
595
607
596 # File system features
608 # File system features
597
609
598 def checkcase(path):
610 def checkcase(path):
599 """
611 """
600 Check whether the given path is on a case-sensitive filesystem
612 Check whether the given path is on a case-sensitive filesystem
601
613
602 Requires a path (like /foo/.hg) ending with a foldable final
614 Requires a path (like /foo/.hg) ending with a foldable final
603 directory component.
615 directory component.
604 """
616 """
605 s1 = os.stat(path)
617 s1 = os.stat(path)
606 d, b = os.path.split(path)
618 d, b = os.path.split(path)
607 p2 = os.path.join(d, b.upper())
619 p2 = os.path.join(d, b.upper())
608 if path == p2:
620 if path == p2:
609 p2 = os.path.join(d, b.lower())
621 p2 = os.path.join(d, b.lower())
610 try:
622 try:
611 s2 = os.stat(p2)
623 s2 = os.stat(p2)
612 if s2 == s1:
624 if s2 == s1:
613 return False
625 return False
614 return True
626 return True
615 except:
627 except:
616 return True
628 return True
617
629
618 _fspathcache = {}
630 _fspathcache = {}
619 def fspath(name, root):
631 def fspath(name, root):
620 '''Get name in the case stored in the filesystem
632 '''Get name in the case stored in the filesystem
621
633
622 The name is either relative to root, or it is an absolute path starting
634 The name is either relative to root, or it is an absolute path starting
623 with root. Note that this function is unnecessary, and should not be
635 with root. Note that this function is unnecessary, and should not be
624 called, for case-sensitive filesystems (simply because it's expensive).
636 called, for case-sensitive filesystems (simply because it's expensive).
625 '''
637 '''
626 # If name is absolute, make it relative
638 # If name is absolute, make it relative
627 if name.lower().startswith(root.lower()):
639 if name.lower().startswith(root.lower()):
628 l = len(root)
640 l = len(root)
629 if name[l] == os.sep or name[l] == os.altsep:
641 if name[l] == os.sep or name[l] == os.altsep:
630 l = l + 1
642 l = l + 1
631 name = name[l:]
643 name = name[l:]
632
644
633 if not os.path.exists(os.path.join(root, name)):
645 if not os.path.exists(os.path.join(root, name)):
634 return None
646 return None
635
647
636 seps = os.sep
648 seps = os.sep
637 if os.altsep:
649 if os.altsep:
638 seps = seps + os.altsep
650 seps = seps + os.altsep
639 # Protect backslashes. This gets silly very quickly.
651 # Protect backslashes. This gets silly very quickly.
640 seps.replace('\\','\\\\')
652 seps.replace('\\','\\\\')
641 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
653 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
642 dir = os.path.normcase(os.path.normpath(root))
654 dir = os.path.normcase(os.path.normpath(root))
643 result = []
655 result = []
644 for part, sep in pattern.findall(name):
656 for part, sep in pattern.findall(name):
645 if sep:
657 if sep:
646 result.append(sep)
658 result.append(sep)
647 continue
659 continue
648
660
649 if dir not in _fspathcache:
661 if dir not in _fspathcache:
650 _fspathcache[dir] = os.listdir(dir)
662 _fspathcache[dir] = os.listdir(dir)
651 contents = _fspathcache[dir]
663 contents = _fspathcache[dir]
652
664
653 lpart = part.lower()
665 lpart = part.lower()
654 lenp = len(part)
666 lenp = len(part)
655 for n in contents:
667 for n in contents:
656 if lenp == len(n) and n.lower() == lpart:
668 if lenp == len(n) and n.lower() == lpart:
657 result.append(n)
669 result.append(n)
658 break
670 break
659 else:
671 else:
660 # Cannot happen, as the file exists!
672 # Cannot happen, as the file exists!
661 result.append(part)
673 result.append(part)
662 dir = os.path.join(dir, lpart)
674 dir = os.path.join(dir, lpart)
663
675
664 return ''.join(result)
676 return ''.join(result)
665
677
666 def checkexec(path):
678 def checkexec(path):
667 """
679 """
668 Check whether the given path is on a filesystem with UNIX-like exec flags
680 Check whether the given path is on a filesystem with UNIX-like exec flags
669
681
670 Requires a directory (like /foo/.hg)
682 Requires a directory (like /foo/.hg)
671 """
683 """
672
684
673 # VFAT on some Linux versions can flip mode but it doesn't persist
685 # VFAT on some Linux versions can flip mode but it doesn't persist
674 # a FS remount. Frequently we can detect it if files are created
686 # a FS remount. Frequently we can detect it if files are created
675 # with exec bit on.
687 # with exec bit on.
676
688
677 try:
689 try:
678 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
690 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
679 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
691 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
680 try:
692 try:
681 os.close(fh)
693 os.close(fh)
682 m = os.stat(fn).st_mode & 0777
694 m = os.stat(fn).st_mode & 0777
683 new_file_has_exec = m & EXECFLAGS
695 new_file_has_exec = m & EXECFLAGS
684 os.chmod(fn, m ^ EXECFLAGS)
696 os.chmod(fn, m ^ EXECFLAGS)
685 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
697 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
686 finally:
698 finally:
687 os.unlink(fn)
699 os.unlink(fn)
688 except (IOError, OSError):
700 except (IOError, OSError):
689 # we don't care, the user probably won't be able to commit anyway
701 # we don't care, the user probably won't be able to commit anyway
690 return False
702 return False
691 return not (new_file_has_exec or exec_flags_cannot_flip)
703 return not (new_file_has_exec or exec_flags_cannot_flip)
692
704
693 def checklink(path):
705 def checklink(path):
694 """check whether the given path is on a symlink-capable filesystem"""
706 """check whether the given path is on a symlink-capable filesystem"""
695 # mktemp is not racy because symlink creation will fail if the
707 # mktemp is not racy because symlink creation will fail if the
696 # file already exists
708 # file already exists
697 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
709 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
698 try:
710 try:
699 os.symlink(".", name)
711 os.symlink(".", name)
700 os.unlink(name)
712 os.unlink(name)
701 return True
713 return True
702 except (OSError, AttributeError):
714 except (OSError, AttributeError):
703 return False
715 return False
704
716
705 def needbinarypatch():
717 def needbinarypatch():
706 """return True if patches should be applied in binary mode by default."""
718 """return True if patches should be applied in binary mode by default."""
707 return os.name == 'nt'
719 return os.name == 'nt'
708
720
709 def endswithsep(path):
721 def endswithsep(path):
710 '''Check path ends with os.sep or os.altsep.'''
722 '''Check path ends with os.sep or os.altsep.'''
711 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
723 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
712
724
713 def splitpath(path):
725 def splitpath(path):
714 '''Split path by os.sep.
726 '''Split path by os.sep.
715 Note that this function does not use os.altsep because this is
727 Note that this function does not use os.altsep because this is
716 an alternative of simple "xxx.split(os.sep)".
728 an alternative of simple "xxx.split(os.sep)".
717 It is recommended to use os.path.normpath() before using this
729 It is recommended to use os.path.normpath() before using this
718 function if need.'''
730 function if need.'''
719 return path.split(os.sep)
731 return path.split(os.sep)
720
732
721 def gui():
733 def gui():
722 '''Are we running in a GUI?'''
734 '''Are we running in a GUI?'''
723 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
735 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
724
736
725 def mktempcopy(name, emptyok=False, createmode=None):
737 def mktempcopy(name, emptyok=False, createmode=None):
726 """Create a temporary file with the same contents from name
738 """Create a temporary file with the same contents from name
727
739
728 The permission bits are copied from the original file.
740 The permission bits are copied from the original file.
729
741
730 If the temporary file is going to be truncated immediately, you
742 If the temporary file is going to be truncated immediately, you
731 can use emptyok=True as an optimization.
743 can use emptyok=True as an optimization.
732
744
733 Returns the name of the temporary file.
745 Returns the name of the temporary file.
734 """
746 """
735 d, fn = os.path.split(name)
747 d, fn = os.path.split(name)
736 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
748 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
737 os.close(fd)
749 os.close(fd)
738 # Temporary files are created with mode 0600, which is usually not
750 # Temporary files are created with mode 0600, which is usually not
739 # what we want. If the original file already exists, just copy
751 # what we want. If the original file already exists, just copy
740 # its mode. Otherwise, manually obey umask.
752 # its mode. Otherwise, manually obey umask.
741 try:
753 try:
742 st_mode = os.lstat(name).st_mode & 0777
754 st_mode = os.lstat(name).st_mode & 0777
743 except OSError, inst:
755 except OSError, inst:
744 if inst.errno != errno.ENOENT:
756 if inst.errno != errno.ENOENT:
745 raise
757 raise
746 st_mode = createmode
758 st_mode = createmode
747 if st_mode is None:
759 if st_mode is None:
748 st_mode = ~umask
760 st_mode = ~umask
749 st_mode &= 0666
761 st_mode &= 0666
750 os.chmod(temp, st_mode)
762 os.chmod(temp, st_mode)
751 if emptyok:
763 if emptyok:
752 return temp
764 return temp
753 try:
765 try:
754 try:
766 try:
755 ifp = posixfile(name, "rb")
767 ifp = posixfile(name, "rb")
756 except IOError, inst:
768 except IOError, inst:
757 if inst.errno == errno.ENOENT:
769 if inst.errno == errno.ENOENT:
758 return temp
770 return temp
759 if not getattr(inst, 'filename', None):
771 if not getattr(inst, 'filename', None):
760 inst.filename = name
772 inst.filename = name
761 raise
773 raise
762 ofp = posixfile(temp, "wb")
774 ofp = posixfile(temp, "wb")
763 for chunk in filechunkiter(ifp):
775 for chunk in filechunkiter(ifp):
764 ofp.write(chunk)
776 ofp.write(chunk)
765 ifp.close()
777 ifp.close()
766 ofp.close()
778 ofp.close()
767 except:
779 except:
768 try: os.unlink(temp)
780 try: os.unlink(temp)
769 except: pass
781 except: pass
770 raise
782 raise
771 return temp
783 return temp
772
784
773 class atomictempfile(object):
785 class atomictempfile(object):
774 """file-like object that atomically updates a file
786 """file-like object that atomically updates a file
775
787
776 All writes will be redirected to a temporary copy of the original
788 All writes will be redirected to a temporary copy of the original
777 file. When rename is called, the copy is renamed to the original
789 file. When rename is called, the copy is renamed to the original
778 name, making the changes visible.
790 name, making the changes visible.
779 """
791 """
780 def __init__(self, name, mode='w+b', createmode=None):
792 def __init__(self, name, mode='w+b', createmode=None):
781 self.__name = name
793 self.__name = name
782 self._fp = None
794 self._fp = None
783 self.temp = mktempcopy(name, emptyok=('w' in mode),
795 self.temp = mktempcopy(name, emptyok=('w' in mode),
784 createmode=createmode)
796 createmode=createmode)
785 self._fp = posixfile(self.temp, mode)
797 self._fp = posixfile(self.temp, mode)
786
798
787 def __getattr__(self, name):
799 def __getattr__(self, name):
788 return getattr(self._fp, name)
800 return getattr(self._fp, name)
789
801
790 def rename(self):
802 def rename(self):
791 if not self._fp.closed:
803 if not self._fp.closed:
792 self._fp.close()
804 self._fp.close()
793 rename(self.temp, localpath(self.__name))
805 rename(self.temp, localpath(self.__name))
794
806
795 def __del__(self):
807 def __del__(self):
796 if not self._fp:
808 if not self._fp:
797 return
809 return
798 if not self._fp.closed:
810 if not self._fp.closed:
799 try:
811 try:
800 os.unlink(self.temp)
812 os.unlink(self.temp)
801 except: pass
813 except: pass
802 self._fp.close()
814 self._fp.close()
803
815
804 def makedirs(name, mode=None):
816 def makedirs(name, mode=None):
805 """recursive directory creation with parent mode inheritance"""
817 """recursive directory creation with parent mode inheritance"""
806 try:
818 try:
807 os.mkdir(name)
819 os.mkdir(name)
808 if mode is not None:
820 if mode is not None:
809 os.chmod(name, mode)
821 os.chmod(name, mode)
810 return
822 return
811 except OSError, err:
823 except OSError, err:
812 if err.errno == errno.EEXIST:
824 if err.errno == errno.EEXIST:
813 return
825 return
814 if err.errno != errno.ENOENT:
826 if err.errno != errno.ENOENT:
815 raise
827 raise
816 parent = os.path.abspath(os.path.dirname(name))
828 parent = os.path.abspath(os.path.dirname(name))
817 makedirs(parent, mode)
829 makedirs(parent, mode)
818 makedirs(name, mode)
830 makedirs(name, mode)
819
831
820 class opener(object):
832 class opener(object):
821 """Open files relative to a base directory
833 """Open files relative to a base directory
822
834
823 This class is used to hide the details of COW semantics and
835 This class is used to hide the details of COW semantics and
824 remote file access from higher level code.
836 remote file access from higher level code.
825 """
837 """
826 def __init__(self, base, audit=True):
838 def __init__(self, base, audit=True):
827 self.base = base
839 self.base = base
828 if audit:
840 if audit:
829 self.audit_path = path_auditor(base)
841 self.audit_path = path_auditor(base)
830 else:
842 else:
831 self.audit_path = always
843 self.audit_path = always
832 self.createmode = None
844 self.createmode = None
833
845
834 @propertycache
846 @propertycache
835 def _can_symlink(self):
847 def _can_symlink(self):
836 return checklink(self.base)
848 return checklink(self.base)
837
849
838 def _fixfilemode(self, name):
850 def _fixfilemode(self, name):
839 if self.createmode is None:
851 if self.createmode is None:
840 return
852 return
841 os.chmod(name, self.createmode & 0666)
853 os.chmod(name, self.createmode & 0666)
842
854
843 def __call__(self, path, mode="r", text=False, atomictemp=False):
855 def __call__(self, path, mode="r", text=False, atomictemp=False):
844 self.audit_path(path)
856 self.audit_path(path)
845 f = os.path.join(self.base, path)
857 f = os.path.join(self.base, path)
846
858
847 if not text and "b" not in mode:
859 if not text and "b" not in mode:
848 mode += "b" # for that other OS
860 mode += "b" # for that other OS
849
861
850 nlink = -1
862 nlink = -1
851 if mode not in ("r", "rb"):
863 if mode not in ("r", "rb"):
852 try:
864 try:
853 nlink = nlinks(f)
865 nlink = nlinks(f)
854 except OSError:
866 except OSError:
855 nlink = 0
867 nlink = 0
856 d = os.path.dirname(f)
868 d = os.path.dirname(f)
857 if not os.path.isdir(d):
869 if not os.path.isdir(d):
858 makedirs(d, self.createmode)
870 makedirs(d, self.createmode)
859 if atomictemp:
871 if atomictemp:
860 return atomictempfile(f, mode, self.createmode)
872 return atomictempfile(f, mode, self.createmode)
861 if nlink > 1:
873 if nlink > 1:
862 rename(mktempcopy(f), f)
874 rename(mktempcopy(f), f)
863 fp = posixfile(f, mode)
875 fp = posixfile(f, mode)
864 if nlink == 0:
876 if nlink == 0:
865 self._fixfilemode(f)
877 self._fixfilemode(f)
866 return fp
878 return fp
867
879
868 def symlink(self, src, dst):
880 def symlink(self, src, dst):
869 self.audit_path(dst)
881 self.audit_path(dst)
870 linkname = os.path.join(self.base, dst)
882 linkname = os.path.join(self.base, dst)
871 try:
883 try:
872 os.unlink(linkname)
884 os.unlink(linkname)
873 except OSError:
885 except OSError:
874 pass
886 pass
875
887
876 dirname = os.path.dirname(linkname)
888 dirname = os.path.dirname(linkname)
877 if not os.path.exists(dirname):
889 if not os.path.exists(dirname):
878 makedirs(dirname, self.createmode)
890 makedirs(dirname, self.createmode)
879
891
880 if self._can_symlink:
892 if self._can_symlink:
881 try:
893 try:
882 os.symlink(src, linkname)
894 os.symlink(src, linkname)
883 except OSError, err:
895 except OSError, err:
884 raise OSError(err.errno, _('could not symlink to %r: %s') %
896 raise OSError(err.errno, _('could not symlink to %r: %s') %
885 (src, err.strerror), linkname)
897 (src, err.strerror), linkname)
886 else:
898 else:
887 f = self(dst, "w")
899 f = self(dst, "w")
888 f.write(src)
900 f.write(src)
889 f.close()
901 f.close()
890 self._fixfilemode(dst)
902 self._fixfilemode(dst)
891
903
892 class chunkbuffer(object):
904 class chunkbuffer(object):
893 """Allow arbitrary sized chunks of data to be efficiently read from an
905 """Allow arbitrary sized chunks of data to be efficiently read from an
894 iterator over chunks of arbitrary size."""
906 iterator over chunks of arbitrary size."""
895
907
896 def __init__(self, in_iter):
908 def __init__(self, in_iter):
897 """in_iter is the iterator that's iterating over the input chunks.
909 """in_iter is the iterator that's iterating over the input chunks.
898 targetsize is how big a buffer to try to maintain."""
910 targetsize is how big a buffer to try to maintain."""
899 self.iter = iter(in_iter)
911 self.iter = iter(in_iter)
900 self.buf = ''
912 self.buf = ''
901 self.targetsize = 2**16
913 self.targetsize = 2**16
902
914
903 def read(self, l):
915 def read(self, l):
904 """Read L bytes of data from the iterator of chunks of data.
916 """Read L bytes of data from the iterator of chunks of data.
905 Returns less than L bytes if the iterator runs dry."""
917 Returns less than L bytes if the iterator runs dry."""
906 if l > len(self.buf) and self.iter:
918 if l > len(self.buf) and self.iter:
907 # Clamp to a multiple of self.targetsize
919 # Clamp to a multiple of self.targetsize
908 targetsize = max(l, self.targetsize)
920 targetsize = max(l, self.targetsize)
909 collector = cStringIO.StringIO()
921 collector = cStringIO.StringIO()
910 collector.write(self.buf)
922 collector.write(self.buf)
911 collected = len(self.buf)
923 collected = len(self.buf)
912 for chunk in self.iter:
924 for chunk in self.iter:
913 collector.write(chunk)
925 collector.write(chunk)
914 collected += len(chunk)
926 collected += len(chunk)
915 if collected >= targetsize:
927 if collected >= targetsize:
916 break
928 break
917 if collected < targetsize:
929 if collected < targetsize:
918 self.iter = False
930 self.iter = False
919 self.buf = collector.getvalue()
931 self.buf = collector.getvalue()
920 if len(self.buf) == l:
932 if len(self.buf) == l:
921 s, self.buf = str(self.buf), ''
933 s, self.buf = str(self.buf), ''
922 else:
934 else:
923 s, self.buf = self.buf[:l], buffer(self.buf, l)
935 s, self.buf = self.buf[:l], buffer(self.buf, l)
924 return s
936 return s
925
937
926 def filechunkiter(f, size=65536, limit=None):
938 def filechunkiter(f, size=65536, limit=None):
927 """Create a generator that produces the data in the file size
939 """Create a generator that produces the data in the file size
928 (default 65536) bytes at a time, up to optional limit (default is
940 (default 65536) bytes at a time, up to optional limit (default is
929 to read all data). Chunks may be less than size bytes if the
941 to read all data). Chunks may be less than size bytes if the
930 chunk is the last chunk in the file, or the file is a socket or
942 chunk is the last chunk in the file, or the file is a socket or
931 some other type of file that sometimes reads less data than is
943 some other type of file that sometimes reads less data than is
932 requested."""
944 requested."""
933 assert size >= 0
945 assert size >= 0
934 assert limit is None or limit >= 0
946 assert limit is None or limit >= 0
935 while True:
947 while True:
936 if limit is None:
948 if limit is None:
937 nbytes = size
949 nbytes = size
938 else:
950 else:
939 nbytes = min(limit, size)
951 nbytes = min(limit, size)
940 s = nbytes and f.read(nbytes)
952 s = nbytes and f.read(nbytes)
941 if not s:
953 if not s:
942 break
954 break
943 if limit:
955 if limit:
944 limit -= len(s)
956 limit -= len(s)
945 yield s
957 yield s
946
958
947 def makedate():
959 def makedate():
948 lt = time.localtime()
960 lt = time.localtime()
949 if lt[8] == 1 and time.daylight:
961 if lt[8] == 1 and time.daylight:
950 tz = time.altzone
962 tz = time.altzone
951 else:
963 else:
952 tz = time.timezone
964 tz = time.timezone
953 return time.mktime(lt), tz
965 return time.mktime(lt), tz
954
966
955 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
967 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
956 """represent a (unixtime, offset) tuple as a localized time.
968 """represent a (unixtime, offset) tuple as a localized time.
957 unixtime is seconds since the epoch, and offset is the time zone's
969 unixtime is seconds since the epoch, and offset is the time zone's
958 number of seconds away from UTC. if timezone is false, do not
970 number of seconds away from UTC. if timezone is false, do not
959 append time zone to string."""
971 append time zone to string."""
960 t, tz = date or makedate()
972 t, tz = date or makedate()
961 if "%1" in format or "%2" in format:
973 if "%1" in format or "%2" in format:
962 sign = (tz > 0) and "-" or "+"
974 sign = (tz > 0) and "-" or "+"
963 minutes = abs(tz) // 60
975 minutes = abs(tz) // 60
964 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
976 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
965 format = format.replace("%2", "%02d" % (minutes % 60))
977 format = format.replace("%2", "%02d" % (minutes % 60))
966 s = time.strftime(format, time.gmtime(float(t) - tz))
978 s = time.strftime(format, time.gmtime(float(t) - tz))
967 return s
979 return s
968
980
969 def shortdate(date=None):
981 def shortdate(date=None):
970 """turn (timestamp, tzoff) tuple into iso 8631 date."""
982 """turn (timestamp, tzoff) tuple into iso 8631 date."""
971 return datestr(date, format='%Y-%m-%d')
983 return datestr(date, format='%Y-%m-%d')
972
984
973 def strdate(string, format, defaults=[]):
985 def strdate(string, format, defaults=[]):
974 """parse a localized time string and return a (unixtime, offset) tuple.
986 """parse a localized time string and return a (unixtime, offset) tuple.
975 if the string cannot be parsed, ValueError is raised."""
987 if the string cannot be parsed, ValueError is raised."""
976 def timezone(string):
988 def timezone(string):
977 tz = string.split()[-1]
989 tz = string.split()[-1]
978 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
990 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
979 sign = (tz[0] == "+") and 1 or -1
991 sign = (tz[0] == "+") and 1 or -1
980 hours = int(tz[1:3])
992 hours = int(tz[1:3])
981 minutes = int(tz[3:5])
993 minutes = int(tz[3:5])
982 return -sign * (hours * 60 + minutes) * 60
994 return -sign * (hours * 60 + minutes) * 60
983 if tz == "GMT" or tz == "UTC":
995 if tz == "GMT" or tz == "UTC":
984 return 0
996 return 0
985 return None
997 return None
986
998
987 # NOTE: unixtime = localunixtime + offset
999 # NOTE: unixtime = localunixtime + offset
988 offset, date = timezone(string), string
1000 offset, date = timezone(string), string
989 if offset != None:
1001 if offset != None:
990 date = " ".join(string.split()[:-1])
1002 date = " ".join(string.split()[:-1])
991
1003
992 # add missing elements from defaults
1004 # add missing elements from defaults
993 for part in defaults:
1005 for part in defaults:
994 found = [True for p in part if ("%"+p) in format]
1006 found = [True for p in part if ("%"+p) in format]
995 if not found:
1007 if not found:
996 date += "@" + defaults[part]
1008 date += "@" + defaults[part]
997 format += "@%" + part[0]
1009 format += "@%" + part[0]
998
1010
999 timetuple = time.strptime(date, format)
1011 timetuple = time.strptime(date, format)
1000 localunixtime = int(calendar.timegm(timetuple))
1012 localunixtime = int(calendar.timegm(timetuple))
1001 if offset is None:
1013 if offset is None:
1002 # local timezone
1014 # local timezone
1003 unixtime = int(time.mktime(timetuple))
1015 unixtime = int(time.mktime(timetuple))
1004 offset = unixtime - localunixtime
1016 offset = unixtime - localunixtime
1005 else:
1017 else:
1006 unixtime = localunixtime + offset
1018 unixtime = localunixtime + offset
1007 return unixtime, offset
1019 return unixtime, offset
1008
1020
1009 def parsedate(date, formats=None, defaults=None):
1021 def parsedate(date, formats=None, defaults=None):
1010 """parse a localized date/time string and return a (unixtime, offset) tuple.
1022 """parse a localized date/time string and return a (unixtime, offset) tuple.
1011
1023
1012 The date may be a "unixtime offset" string or in one of the specified
1024 The date may be a "unixtime offset" string or in one of the specified
1013 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1025 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1014 """
1026 """
1015 if not date:
1027 if not date:
1016 return 0, 0
1028 return 0, 0
1017 if isinstance(date, tuple) and len(date) == 2:
1029 if isinstance(date, tuple) and len(date) == 2:
1018 return date
1030 return date
1019 if not formats:
1031 if not formats:
1020 formats = defaultdateformats
1032 formats = defaultdateformats
1021 date = date.strip()
1033 date = date.strip()
1022 try:
1034 try:
1023 when, offset = map(int, date.split(' '))
1035 when, offset = map(int, date.split(' '))
1024 except ValueError:
1036 except ValueError:
1025 # fill out defaults
1037 # fill out defaults
1026 if not defaults:
1038 if not defaults:
1027 defaults = {}
1039 defaults = {}
1028 now = makedate()
1040 now = makedate()
1029 for part in "d mb yY HI M S".split():
1041 for part in "d mb yY HI M S".split():
1030 if part not in defaults:
1042 if part not in defaults:
1031 if part[0] in "HMS":
1043 if part[0] in "HMS":
1032 defaults[part] = "00"
1044 defaults[part] = "00"
1033 else:
1045 else:
1034 defaults[part] = datestr(now, "%" + part[0])
1046 defaults[part] = datestr(now, "%" + part[0])
1035
1047
1036 for format in formats:
1048 for format in formats:
1037 try:
1049 try:
1038 when, offset = strdate(date, format, defaults)
1050 when, offset = strdate(date, format, defaults)
1039 except (ValueError, OverflowError):
1051 except (ValueError, OverflowError):
1040 pass
1052 pass
1041 else:
1053 else:
1042 break
1054 break
1043 else:
1055 else:
1044 raise Abort(_('invalid date: %r ') % date)
1056 raise Abort(_('invalid date: %r ') % date)
1045 # validate explicit (probably user-specified) date and
1057 # validate explicit (probably user-specified) date and
1046 # time zone offset. values must fit in signed 32 bits for
1058 # time zone offset. values must fit in signed 32 bits for
1047 # current 32-bit linux runtimes. timezones go from UTC-12
1059 # current 32-bit linux runtimes. timezones go from UTC-12
1048 # to UTC+14
1060 # to UTC+14
1049 if abs(when) > 0x7fffffff:
1061 if abs(when) > 0x7fffffff:
1050 raise Abort(_('date exceeds 32 bits: %d') % when)
1062 raise Abort(_('date exceeds 32 bits: %d') % when)
1051 if offset < -50400 or offset > 43200:
1063 if offset < -50400 or offset > 43200:
1052 raise Abort(_('impossible time zone offset: %d') % offset)
1064 raise Abort(_('impossible time zone offset: %d') % offset)
1053 return when, offset
1065 return when, offset
1054
1066
1055 def matchdate(date):
1067 def matchdate(date):
1056 """Return a function that matches a given date match specifier
1068 """Return a function that matches a given date match specifier
1057
1069
1058 Formats include:
1070 Formats include:
1059
1071
1060 '{date}' match a given date to the accuracy provided
1072 '{date}' match a given date to the accuracy provided
1061
1073
1062 '<{date}' on or before a given date
1074 '<{date}' on or before a given date
1063
1075
1064 '>{date}' on or after a given date
1076 '>{date}' on or after a given date
1065
1077
1066 """
1078 """
1067
1079
1068 def lower(date):
1080 def lower(date):
1069 d = dict(mb="1", d="1")
1081 d = dict(mb="1", d="1")
1070 return parsedate(date, extendeddateformats, d)[0]
1082 return parsedate(date, extendeddateformats, d)[0]
1071
1083
1072 def upper(date):
1084 def upper(date):
1073 d = dict(mb="12", HI="23", M="59", S="59")
1085 d = dict(mb="12", HI="23", M="59", S="59")
1074 for days in "31 30 29".split():
1086 for days in "31 30 29".split():
1075 try:
1087 try:
1076 d["d"] = days
1088 d["d"] = days
1077 return parsedate(date, extendeddateformats, d)[0]
1089 return parsedate(date, extendeddateformats, d)[0]
1078 except:
1090 except:
1079 pass
1091 pass
1080 d["d"] = "28"
1092 d["d"] = "28"
1081 return parsedate(date, extendeddateformats, d)[0]
1093 return parsedate(date, extendeddateformats, d)[0]
1082
1094
1083 date = date.strip()
1095 date = date.strip()
1084 if date[0] == "<":
1096 if date[0] == "<":
1085 when = upper(date[1:])
1097 when = upper(date[1:])
1086 return lambda x: x <= when
1098 return lambda x: x <= when
1087 elif date[0] == ">":
1099 elif date[0] == ">":
1088 when = lower(date[1:])
1100 when = lower(date[1:])
1089 return lambda x: x >= when
1101 return lambda x: x >= when
1090 elif date[0] == "-":
1102 elif date[0] == "-":
1091 try:
1103 try:
1092 days = int(date[1:])
1104 days = int(date[1:])
1093 except ValueError:
1105 except ValueError:
1094 raise Abort(_("invalid day spec: %s") % date[1:])
1106 raise Abort(_("invalid day spec: %s") % date[1:])
1095 when = makedate()[0] - days * 3600 * 24
1107 when = makedate()[0] - days * 3600 * 24
1096 return lambda x: x >= when
1108 return lambda x: x >= when
1097 elif " to " in date:
1109 elif " to " in date:
1098 a, b = date.split(" to ")
1110 a, b = date.split(" to ")
1099 start, stop = lower(a), upper(b)
1111 start, stop = lower(a), upper(b)
1100 return lambda x: x >= start and x <= stop
1112 return lambda x: x >= start and x <= stop
1101 else:
1113 else:
1102 start, stop = lower(date), upper(date)
1114 start, stop = lower(date), upper(date)
1103 return lambda x: x >= start and x <= stop
1115 return lambda x: x >= start and x <= stop
1104
1116
1105 def shortuser(user):
1117 def shortuser(user):
1106 """Return a short representation of a user name or email address."""
1118 """Return a short representation of a user name or email address."""
1107 f = user.find('@')
1119 f = user.find('@')
1108 if f >= 0:
1120 if f >= 0:
1109 user = user[:f]
1121 user = user[:f]
1110 f = user.find('<')
1122 f = user.find('<')
1111 if f >= 0:
1123 if f >= 0:
1112 user = user[f + 1:]
1124 user = user[f + 1:]
1113 f = user.find(' ')
1125 f = user.find(' ')
1114 if f >= 0:
1126 if f >= 0:
1115 user = user[:f]
1127 user = user[:f]
1116 f = user.find('.')
1128 f = user.find('.')
1117 if f >= 0:
1129 if f >= 0:
1118 user = user[:f]
1130 user = user[:f]
1119 return user
1131 return user
1120
1132
1121 def email(author):
1133 def email(author):
1122 '''get email of author.'''
1134 '''get email of author.'''
1123 r = author.find('>')
1135 r = author.find('>')
1124 if r == -1:
1136 if r == -1:
1125 r = None
1137 r = None
1126 return author[author.find('<') + 1:r]
1138 return author[author.find('<') + 1:r]
1127
1139
1128 def ellipsis(text, maxlength=400):
1140 def ellipsis(text, maxlength=400):
1129 """Trim string to at most maxlength (default: 400) characters."""
1141 """Trim string to at most maxlength (default: 400) characters."""
1130 if len(text) <= maxlength:
1142 if len(text) <= maxlength:
1131 return text
1143 return text
1132 else:
1144 else:
1133 return "%s..." % (text[:maxlength - 3])
1145 return "%s..." % (text[:maxlength - 3])
1134
1146
1135 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1147 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1136 '''yield every hg repository under path, recursively.'''
1148 '''yield every hg repository under path, recursively.'''
1137 def errhandler(err):
1149 def errhandler(err):
1138 if err.filename == path:
1150 if err.filename == path:
1139 raise err
1151 raise err
1140 if followsym and hasattr(os.path, 'samestat'):
1152 if followsym and hasattr(os.path, 'samestat'):
1141 def _add_dir_if_not_there(dirlst, dirname):
1153 def _add_dir_if_not_there(dirlst, dirname):
1142 match = False
1154 match = False
1143 samestat = os.path.samestat
1155 samestat = os.path.samestat
1144 dirstat = os.stat(dirname)
1156 dirstat = os.stat(dirname)
1145 for lstdirstat in dirlst:
1157 for lstdirstat in dirlst:
1146 if samestat(dirstat, lstdirstat):
1158 if samestat(dirstat, lstdirstat):
1147 match = True
1159 match = True
1148 break
1160 break
1149 if not match:
1161 if not match:
1150 dirlst.append(dirstat)
1162 dirlst.append(dirstat)
1151 return not match
1163 return not match
1152 else:
1164 else:
1153 followsym = False
1165 followsym = False
1154
1166
1155 if (seen_dirs is None) and followsym:
1167 if (seen_dirs is None) and followsym:
1156 seen_dirs = []
1168 seen_dirs = []
1157 _add_dir_if_not_there(seen_dirs, path)
1169 _add_dir_if_not_there(seen_dirs, path)
1158 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1170 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1159 dirs.sort()
1171 dirs.sort()
1160 if '.hg' in dirs:
1172 if '.hg' in dirs:
1161 yield root # found a repository
1173 yield root # found a repository
1162 qroot = os.path.join(root, '.hg', 'patches')
1174 qroot = os.path.join(root, '.hg', 'patches')
1163 if os.path.isdir(os.path.join(qroot, '.hg')):
1175 if os.path.isdir(os.path.join(qroot, '.hg')):
1164 yield qroot # we have a patch queue repo here
1176 yield qroot # we have a patch queue repo here
1165 if recurse:
1177 if recurse:
1166 # avoid recursing inside the .hg directory
1178 # avoid recursing inside the .hg directory
1167 dirs.remove('.hg')
1179 dirs.remove('.hg')
1168 else:
1180 else:
1169 dirs[:] = [] # don't descend further
1181 dirs[:] = [] # don't descend further
1170 elif followsym:
1182 elif followsym:
1171 newdirs = []
1183 newdirs = []
1172 for d in dirs:
1184 for d in dirs:
1173 fname = os.path.join(root, d)
1185 fname = os.path.join(root, d)
1174 if _add_dir_if_not_there(seen_dirs, fname):
1186 if _add_dir_if_not_there(seen_dirs, fname):
1175 if os.path.islink(fname):
1187 if os.path.islink(fname):
1176 for hgname in walkrepos(fname, True, seen_dirs):
1188 for hgname in walkrepos(fname, True, seen_dirs):
1177 yield hgname
1189 yield hgname
1178 else:
1190 else:
1179 newdirs.append(d)
1191 newdirs.append(d)
1180 dirs[:] = newdirs
1192 dirs[:] = newdirs
1181
1193
1182 _rcpath = None
1194 _rcpath = None
1183
1195
1184 def os_rcpath():
1196 def os_rcpath():
1185 '''return default os-specific hgrc search path'''
1197 '''return default os-specific hgrc search path'''
1186 path = system_rcpath()
1198 path = system_rcpath()
1187 path.extend(user_rcpath())
1199 path.extend(user_rcpath())
1188 path = [os.path.normpath(f) for f in path]
1200 path = [os.path.normpath(f) for f in path]
1189 return path
1201 return path
1190
1202
1191 def rcpath():
1203 def rcpath():
1192 '''return hgrc search path. if env var HGRCPATH is set, use it.
1204 '''return hgrc search path. if env var HGRCPATH is set, use it.
1193 for each item in path, if directory, use files ending in .rc,
1205 for each item in path, if directory, use files ending in .rc,
1194 else use item.
1206 else use item.
1195 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1207 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1196 if no HGRCPATH, use default os-specific path.'''
1208 if no HGRCPATH, use default os-specific path.'''
1197 global _rcpath
1209 global _rcpath
1198 if _rcpath is None:
1210 if _rcpath is None:
1199 if 'HGRCPATH' in os.environ:
1211 if 'HGRCPATH' in os.environ:
1200 _rcpath = []
1212 _rcpath = []
1201 for p in os.environ['HGRCPATH'].split(os.pathsep):
1213 for p in os.environ['HGRCPATH'].split(os.pathsep):
1202 if not p:
1214 if not p:
1203 continue
1215 continue
1204 p = expandpath(p)
1216 p = expandpath(p)
1205 if os.path.isdir(p):
1217 if os.path.isdir(p):
1206 for f, kind in osutil.listdir(p):
1218 for f, kind in osutil.listdir(p):
1207 if f.endswith('.rc'):
1219 if f.endswith('.rc'):
1208 _rcpath.append(os.path.join(p, f))
1220 _rcpath.append(os.path.join(p, f))
1209 else:
1221 else:
1210 _rcpath.append(p)
1222 _rcpath.append(p)
1211 else:
1223 else:
1212 _rcpath = os_rcpath()
1224 _rcpath = os_rcpath()
1213 return _rcpath
1225 return _rcpath
1214
1226
1215 def bytecount(nbytes):
1227 def bytecount(nbytes):
1216 '''return byte count formatted as readable string, with units'''
1228 '''return byte count formatted as readable string, with units'''
1217
1229
1218 units = (
1230 units = (
1219 (100, 1 << 30, _('%.0f GB')),
1231 (100, 1 << 30, _('%.0f GB')),
1220 (10, 1 << 30, _('%.1f GB')),
1232 (10, 1 << 30, _('%.1f GB')),
1221 (1, 1 << 30, _('%.2f GB')),
1233 (1, 1 << 30, _('%.2f GB')),
1222 (100, 1 << 20, _('%.0f MB')),
1234 (100, 1 << 20, _('%.0f MB')),
1223 (10, 1 << 20, _('%.1f MB')),
1235 (10, 1 << 20, _('%.1f MB')),
1224 (1, 1 << 20, _('%.2f MB')),
1236 (1, 1 << 20, _('%.2f MB')),
1225 (100, 1 << 10, _('%.0f KB')),
1237 (100, 1 << 10, _('%.0f KB')),
1226 (10, 1 << 10, _('%.1f KB')),
1238 (10, 1 << 10, _('%.1f KB')),
1227 (1, 1 << 10, _('%.2f KB')),
1239 (1, 1 << 10, _('%.2f KB')),
1228 (1, 1, _('%.0f bytes')),
1240 (1, 1, _('%.0f bytes')),
1229 )
1241 )
1230
1242
1231 for multiplier, divisor, format in units:
1243 for multiplier, divisor, format in units:
1232 if nbytes >= divisor * multiplier:
1244 if nbytes >= divisor * multiplier:
1233 return format % (nbytes / float(divisor))
1245 return format % (nbytes / float(divisor))
1234 return units[-1][2] % nbytes
1246 return units[-1][2] % nbytes
1235
1247
1236 def drop_scheme(scheme, path):
1248 def drop_scheme(scheme, path):
1237 sc = scheme + ':'
1249 sc = scheme + ':'
1238 if path.startswith(sc):
1250 if path.startswith(sc):
1239 path = path[len(sc):]
1251 path = path[len(sc):]
1240 if path.startswith('//'):
1252 if path.startswith('//'):
1241 if scheme == 'file':
1253 if scheme == 'file':
1242 i = path.find('/', 2)
1254 i = path.find('/', 2)
1243 if i == -1:
1255 if i == -1:
1244 return ''
1256 return ''
1245 # On Windows, absolute paths are rooted at the current drive
1257 # On Windows, absolute paths are rooted at the current drive
1246 # root. On POSIX they are rooted at the file system root.
1258 # root. On POSIX they are rooted at the file system root.
1247 if os.name == 'nt':
1259 if os.name == 'nt':
1248 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1260 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1249 path = os.path.join(droot, path[i + 1:])
1261 path = os.path.join(droot, path[i + 1:])
1250 else:
1262 else:
1251 path = path[i:]
1263 path = path[i:]
1252 else:
1264 else:
1253 path = path[2:]
1265 path = path[2:]
1254 return path
1266 return path
1255
1267
1256 def uirepr(s):
1268 def uirepr(s):
1257 # Avoid double backslash in Windows path repr()
1269 # Avoid double backslash in Windows path repr()
1258 return repr(s).replace('\\\\', '\\')
1270 return repr(s).replace('\\\\', '\\')
1259
1271
1260 #### naming convention of below implementation follows 'textwrap' module
1272 #### naming convention of below implementation follows 'textwrap' module
1261
1273
1262 class MBTextWrapper(textwrap.TextWrapper):
1274 class MBTextWrapper(textwrap.TextWrapper):
1263 def __init__(self, **kwargs):
1275 def __init__(self, **kwargs):
1264 textwrap.TextWrapper.__init__(self, **kwargs)
1276 textwrap.TextWrapper.__init__(self, **kwargs)
1265
1277
1266 def _cutdown(self, str, space_left):
1278 def _cutdown(self, str, space_left):
1267 l = 0
1279 l = 0
1268 ucstr = unicode(str, encoding.encoding)
1280 ucstr = unicode(str, encoding.encoding)
1269 w = unicodedata.east_asian_width
1281 w = unicodedata.east_asian_width
1270 for i in xrange(len(ucstr)):
1282 for i in xrange(len(ucstr)):
1271 l += w(ucstr[i]) in 'WFA' and 2 or 1
1283 l += w(ucstr[i]) in 'WFA' and 2 or 1
1272 if space_left < l:
1284 if space_left < l:
1273 return (ucstr[:i].encode(encoding.encoding),
1285 return (ucstr[:i].encode(encoding.encoding),
1274 ucstr[i:].encode(encoding.encoding))
1286 ucstr[i:].encode(encoding.encoding))
1275 return str, ''
1287 return str, ''
1276
1288
1277 # ----------------------------------------
1289 # ----------------------------------------
1278 # overriding of base class
1290 # overriding of base class
1279
1291
1280 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1292 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1281 space_left = max(width - cur_len, 1)
1293 space_left = max(width - cur_len, 1)
1282
1294
1283 if self.break_long_words:
1295 if self.break_long_words:
1284 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1296 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1285 cur_line.append(cut)
1297 cur_line.append(cut)
1286 reversed_chunks[-1] = res
1298 reversed_chunks[-1] = res
1287 elif not cur_line:
1299 elif not cur_line:
1288 cur_line.append(reversed_chunks.pop())
1300 cur_line.append(reversed_chunks.pop())
1289
1301
1290 #### naming convention of above implementation follows 'textwrap' module
1302 #### naming convention of above implementation follows 'textwrap' module
1291
1303
1292 def wrap(line, width=None, initindent='', hangindent=''):
1304 def wrap(line, width=None, initindent='', hangindent=''):
1293 if width is None:
1305 if width is None:
1294 width = termwidth() - 2
1306 width = termwidth() - 2
1295 maxindent = max(len(hangindent), len(initindent))
1307 maxindent = max(len(hangindent), len(initindent))
1296 if width <= maxindent:
1308 if width <= maxindent:
1297 # adjust for weird terminal size
1309 # adjust for weird terminal size
1298 width = max(78, maxindent + 1)
1310 width = max(78, maxindent + 1)
1299 wrapper = MBTextWrapper(width=width,
1311 wrapper = MBTextWrapper(width=width,
1300 initial_indent=initindent,
1312 initial_indent=initindent,
1301 subsequent_indent=hangindent)
1313 subsequent_indent=hangindent)
1302 return wrapper.fill(line)
1314 return wrapper.fill(line)
1303
1315
1304 def iterlines(iterator):
1316 def iterlines(iterator):
1305 for chunk in iterator:
1317 for chunk in iterator:
1306 for line in chunk.splitlines():
1318 for line in chunk.splitlines():
1307 yield line
1319 yield line
1308
1320
1309 def expandpath(path):
1321 def expandpath(path):
1310 return os.path.expanduser(os.path.expandvars(path))
1322 return os.path.expanduser(os.path.expandvars(path))
1311
1323
1312 def hgcmd():
1324 def hgcmd():
1313 """Return the command used to execute current hg
1325 """Return the command used to execute current hg
1314
1326
1315 This is different from hgexecutable() because on Windows we want
1327 This is different from hgexecutable() because on Windows we want
1316 to avoid things opening new shell windows like batch files, so we
1328 to avoid things opening new shell windows like batch files, so we
1317 get either the python call or current executable.
1329 get either the python call or current executable.
1318 """
1330 """
1319 if main_is_frozen():
1331 if main_is_frozen():
1320 return [sys.executable]
1332 return [sys.executable]
1321 return gethgcmd()
1333 return gethgcmd()
1322
1334
1323 def rundetached(args, condfn):
1335 def rundetached(args, condfn):
1324 """Execute the argument list in a detached process.
1336 """Execute the argument list in a detached process.
1325
1337
1326 condfn is a callable which is called repeatedly and should return
1338 condfn is a callable which is called repeatedly and should return
1327 True once the child process is known to have started successfully.
1339 True once the child process is known to have started successfully.
1328 At this point, the child process PID is returned. If the child
1340 At this point, the child process PID is returned. If the child
1329 process fails to start or finishes before condfn() evaluates to
1341 process fails to start or finishes before condfn() evaluates to
1330 True, return -1.
1342 True, return -1.
1331 """
1343 """
1332 # Windows case is easier because the child process is either
1344 # Windows case is easier because the child process is either
1333 # successfully starting and validating the condition or exiting
1345 # successfully starting and validating the condition or exiting
1334 # on failure. We just poll on its PID. On Unix, if the child
1346 # on failure. We just poll on its PID. On Unix, if the child
1335 # process fails to start, it will be left in a zombie state until
1347 # process fails to start, it will be left in a zombie state until
1336 # the parent wait on it, which we cannot do since we expect a long
1348 # the parent wait on it, which we cannot do since we expect a long
1337 # running process on success. Instead we listen for SIGCHLD telling
1349 # running process on success. Instead we listen for SIGCHLD telling
1338 # us our child process terminated.
1350 # us our child process terminated.
1339 terminated = set()
1351 terminated = set()
1340 def handler(signum, frame):
1352 def handler(signum, frame):
1341 terminated.add(os.wait())
1353 terminated.add(os.wait())
1342 prevhandler = None
1354 prevhandler = None
1343 if hasattr(signal, 'SIGCHLD'):
1355 if hasattr(signal, 'SIGCHLD'):
1344 prevhandler = signal.signal(signal.SIGCHLD, handler)
1356 prevhandler = signal.signal(signal.SIGCHLD, handler)
1345 try:
1357 try:
1346 pid = spawndetached(args)
1358 pid = spawndetached(args)
1347 while not condfn():
1359 while not condfn():
1348 if ((pid in terminated or not testpid(pid))
1360 if ((pid in terminated or not testpid(pid))
1349 and not condfn()):
1361 and not condfn()):
1350 return -1
1362 return -1
1351 time.sleep(0.1)
1363 time.sleep(0.1)
1352 return pid
1364 return pid
1353 finally:
1365 finally:
1354 if prevhandler is not None:
1366 if prevhandler is not None:
1355 signal.signal(signal.SIGCHLD, prevhandler)
1367 signal.signal(signal.SIGCHLD, prevhandler)
1356
1368
1357 try:
1369 try:
1358 any, all = any, all
1370 any, all = any, all
1359 except NameError:
1371 except NameError:
1360 def any(iterable):
1372 def any(iterable):
1361 for i in iterable:
1373 for i in iterable:
1362 if i:
1374 if i:
1363 return True
1375 return True
1364 return False
1376 return False
1365
1377
1366 def all(iterable):
1378 def all(iterable):
1367 for i in iterable:
1379 for i in iterable:
1368 if not i:
1380 if not i:
1369 return False
1381 return False
1370 return True
1382 return True
1371
1383
1372 def termwidth():
1384 def termwidth():
1373 if 'COLUMNS' in os.environ:
1385 if 'COLUMNS' in os.environ:
1374 try:
1386 try:
1375 return int(os.environ['COLUMNS'])
1387 return int(os.environ['COLUMNS'])
1376 except ValueError:
1388 except ValueError:
1377 pass
1389 pass
1378 return termwidth_()
1390 return termwidth_()
@@ -1,56 +1,54 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cp "$TESTDIR"/printenv.py .
3 cp "$TESTDIR"/printenv.py .
4
4
5 hg init test
5 hg init test
6 cd test
6 cd test
7 echo a > a
7 echo a > a
8 hg ci -Ama
8 hg ci -Ama
9
9
10 cd ..
10 cd ..
11 hg clone test test2
11 hg clone test test2
12 cd test2
12 cd test2
13 echo a >> a
13 echo a >> a
14 hg ci -mb
14 hg ci -mb
15
15
16 req() {
16 req() {
17 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
17 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
18 cat hg.pid >> $DAEMON_PIDS
18 cat hg.pid >> $DAEMON_PIDS
19 hg --cwd ../test2 push http://localhost:$HGPORT/ | sed -e "s,:$HGPORT/,:\$HGPORT/,"
19 hg --cwd ../test2 push http://localhost:$HGPORT/ | sed -e "s,:$HGPORT/,:\$HGPORT/,"
20 kill `cat hg.pid`
20 kill `cat hg.pid`
21 echo % serve errors
21 echo % serve errors
22 cat errors.log
22 cat errors.log
23 }
23 }
24
24
25 cd ../test
25 cd ../test
26
26
27 echo % expect ssl error
27 echo % expect ssl error
28 req
28 req
29
29
30 echo % expect authorization error
30 echo % expect authorization error
31 echo '[web]' > .hg/hgrc
31 echo '[web]' > .hg/hgrc
32 echo 'push_ssl = false' >> .hg/hgrc
32 echo 'push_ssl = false' >> .hg/hgrc
33 req
33 req
34
34
35 echo % expect authorization error: must have authorized user
35 echo % expect authorization error: must have authorized user
36 echo 'allow_push = unperson' >> .hg/hgrc
36 echo 'allow_push = unperson' >> .hg/hgrc
37 req
37 req
38
38
39 echo % expect success
39 echo % expect success
40 echo 'allow_push = *' >> .hg/hgrc
40 echo 'allow_push = *' >> .hg/hgrc
41 echo '[hooks]' >> .hg/hgrc
41 echo '[hooks]' >> .hg/hgrc
42 echo 'changegroup = python ../printenv.py changegroup 0 ../urls' >> .hg/hgrc
42 echo 'changegroup = python ../printenv.py changegroup 0' >> .hg/hgrc
43 req
43 req
44
44
45 cat ../urls
46
47 hg rollback
45 hg rollback
48 echo % expect authorization error: all users denied
46 echo % expect authorization error: all users denied
49 echo '[web]' > .hg/hgrc
47 echo '[web]' > .hg/hgrc
50 echo 'push_ssl = false' >> .hg/hgrc
48 echo 'push_ssl = false' >> .hg/hgrc
51 echo 'deny_push = *' >> .hg/hgrc
49 echo 'deny_push = *' >> .hg/hgrc
52 req
50 req
53
51
54 echo % expect authorization error: some users denied, users must be authenticated
52 echo % expect authorization error: some users denied, users must be authenticated
55 echo 'deny_push = unperson' >> .hg/hgrc
53 echo 'deny_push = unperson' >> .hg/hgrc
56 req
54 req
@@ -1,38 +1,38 b''
1 adding a
1 adding a
2 updating to branch default
2 updating to branch default
3 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4 % expect ssl error
4 % expect ssl error
5 pushing to http://localhost:$HGPORT/
5 pushing to http://localhost:$HGPORT/
6 searching for changes
6 searching for changes
7 remote: ssl required
7 remote: ssl required
8 % serve errors
8 % serve errors
9 % expect authorization error
9 % expect authorization error
10 abort: authorization failed
10 abort: authorization failed
11 pushing to http://localhost:$HGPORT/
11 pushing to http://localhost:$HGPORT/
12 searching for changes
12 searching for changes
13 % serve errors
13 % serve errors
14 % expect authorization error: must have authorized user
14 % expect authorization error: must have authorized user
15 abort: authorization failed
15 abort: authorization failed
16 pushing to http://localhost:$HGPORT/
16 pushing to http://localhost:$HGPORT/
17 searching for changes
17 searching for changes
18 % serve errors
18 % serve errors
19 % expect success
19 % expect success
20 pushing to http://localhost:$HGPORT/
20 pushing to http://localhost:$HGPORT/
21 searching for changes
21 searching for changes
22 remote: adding changesets
22 remote: adding changesets
23 remote: adding manifests
23 remote: adding manifests
24 remote: adding file changes
24 remote: adding file changes
25 remote: added 1 changesets with 1 changes to 1 files
25 remote: added 1 changesets with 1 changes to 1 files
26 remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http
26 % serve errors
27 % serve errors
27 changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http
28 rolling back to revision 0 (undo serve)
28 rolling back to revision 0 (undo serve)
29 % expect authorization error: all users denied
29 % expect authorization error: all users denied
30 abort: authorization failed
30 abort: authorization failed
31 pushing to http://localhost:$HGPORT/
31 pushing to http://localhost:$HGPORT/
32 searching for changes
32 searching for changes
33 % serve errors
33 % serve errors
34 % expect authorization error: some users denied, users must be authenticated
34 % expect authorization error: some users denied, users must be authenticated
35 abort: authorization failed
35 abort: authorization failed
36 pushing to http://localhost:$HGPORT/
36 pushing to http://localhost:$HGPORT/
37 searching for changes
37 searching for changes
38 % serve errors
38 % serve errors
General Comments 0
You need to be logged in to leave comments. Login now