##// END OF EJS Templates
templater: return data in increasing chunk sizes...
Brendan Cully -
r7396:526c40a7 default
parent child Browse files
Show More
@@ -1,314 +1,314 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes
9 import os, mimetypes
10 from mercurial.node import hex, nullid
10 from mercurial.node import hex, nullid
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import ui, hg, util, hook
12 from mercurial import ui, hg, util, hook
13 from mercurial import revlog, templater, templatefilters
13 from mercurial import revlog, templater, templatefilters
14 from common import get_mtime, style_map, ErrorResponse
14 from common import get_mtime, style_map, ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
16 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
17 from request import wsgirequest
17 from request import wsgirequest
18 import webcommands, protocol, webutil
18 import webcommands, protocol, webutil
19
19
20 perms = {
20 perms = {
21 'changegroup': 'pull',
21 'changegroup': 'pull',
22 'changegroupsubset': 'pull',
22 'changegroupsubset': 'pull',
23 'unbundle': 'push',
23 'unbundle': 'push',
24 'stream_out': 'pull',
24 'stream_out': 'pull',
25 }
25 }
26
26
27 class hgweb(object):
27 class hgweb(object):
28 def __init__(self, repo, name=None):
28 def __init__(self, repo, name=None):
29 if isinstance(repo, str):
29 if isinstance(repo, str):
30 parentui = ui.ui(report_untrusted=False, interactive=False)
30 parentui = ui.ui(report_untrusted=False, interactive=False)
31 self.repo = hg.repository(parentui, repo)
31 self.repo = hg.repository(parentui, repo)
32 else:
32 else:
33 self.repo = repo
33 self.repo = repo
34
34
35 hook.redirect(True)
35 hook.redirect(True)
36 self.mtime = -1
36 self.mtime = -1
37 self.reponame = name
37 self.reponame = name
38 self.archives = 'zip', 'gz', 'bz2'
38 self.archives = 'zip', 'gz', 'bz2'
39 self.stripecount = 1
39 self.stripecount = 1
40 # a repo owner may set web.templates in .hg/hgrc to get any file
40 # a repo owner may set web.templates in .hg/hgrc to get any file
41 # readable by the user running the CGI script
41 # readable by the user running the CGI script
42 self.templatepath = self.config("web", "templates",
42 self.templatepath = self.config("web", "templates",
43 templater.templatepath(),
43 templater.templatepath(),
44 untrusted=False)
44 untrusted=False)
45
45
46 # The CGI scripts are often run by a user different from the repo owner.
46 # The CGI scripts are often run by a user different from the repo owner.
47 # Trust the settings from the .hg/hgrc files by default.
47 # Trust the settings from the .hg/hgrc files by default.
48 def config(self, section, name, default=None, untrusted=True):
48 def config(self, section, name, default=None, untrusted=True):
49 return self.repo.ui.config(section, name, default,
49 return self.repo.ui.config(section, name, default,
50 untrusted=untrusted)
50 untrusted=untrusted)
51
51
52 def configbool(self, section, name, default=False, untrusted=True):
52 def configbool(self, section, name, default=False, untrusted=True):
53 return self.repo.ui.configbool(section, name, default,
53 return self.repo.ui.configbool(section, name, default,
54 untrusted=untrusted)
54 untrusted=untrusted)
55
55
56 def configlist(self, section, name, default=None, untrusted=True):
56 def configlist(self, section, name, default=None, untrusted=True):
57 return self.repo.ui.configlist(section, name, default,
57 return self.repo.ui.configlist(section, name, default,
58 untrusted=untrusted)
58 untrusted=untrusted)
59
59
60 def refresh(self):
60 def refresh(self):
61 mtime = get_mtime(self.repo.root)
61 mtime = get_mtime(self.repo.root)
62 if mtime != self.mtime:
62 if mtime != self.mtime:
63 self.mtime = mtime
63 self.mtime = mtime
64 self.repo = hg.repository(self.repo.ui, self.repo.root)
64 self.repo = hg.repository(self.repo.ui, self.repo.root)
65 self.maxchanges = int(self.config("web", "maxchanges", 10))
65 self.maxchanges = int(self.config("web", "maxchanges", 10))
66 self.stripecount = int(self.config("web", "stripes", 1))
66 self.stripecount = int(self.config("web", "stripes", 1))
67 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
67 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
68 self.maxfiles = int(self.config("web", "maxfiles", 10))
68 self.maxfiles = int(self.config("web", "maxfiles", 10))
69 self.allowpull = self.configbool("web", "allowpull", True)
69 self.allowpull = self.configbool("web", "allowpull", True)
70 self.encoding = self.config("web", "encoding", util._encoding)
70 self.encoding = self.config("web", "encoding", util._encoding)
71
71
72 def run(self):
72 def run(self):
73 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
73 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
74 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
74 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
75 import mercurial.hgweb.wsgicgi as wsgicgi
75 import mercurial.hgweb.wsgicgi as wsgicgi
76 wsgicgi.launch(self)
76 wsgicgi.launch(self)
77
77
78 def __call__(self, env, respond):
78 def __call__(self, env, respond):
79 req = wsgirequest(env, respond)
79 req = wsgirequest(env, respond)
80 return self.run_wsgi(req)
80 return self.run_wsgi(req)
81
81
82 def run_wsgi(self, req):
82 def run_wsgi(self, req):
83
83
84 self.refresh()
84 self.refresh()
85
85
86 # process this if it's a protocol request
86 # process this if it's a protocol request
87 # protocol bits don't need to create any URLs
87 # protocol bits don't need to create any URLs
88 # and the clients always use the old URL structure
88 # and the clients always use the old URL structure
89
89
90 cmd = req.form.get('cmd', [''])[0]
90 cmd = req.form.get('cmd', [''])[0]
91 if cmd and cmd in protocol.__all__:
91 if cmd and cmd in protocol.__all__:
92 try:
92 try:
93 if cmd in perms:
93 if cmd in perms:
94 try:
94 try:
95 self.check_perm(req, perms[cmd])
95 self.check_perm(req, perms[cmd])
96 except ErrorResponse, inst:
96 except ErrorResponse, inst:
97 if cmd == 'unbundle':
97 if cmd == 'unbundle':
98 req.drain()
98 req.drain()
99 raise
99 raise
100 method = getattr(protocol, cmd)
100 method = getattr(protocol, cmd)
101 return method(self.repo, req)
101 return method(self.repo, req)
102 except ErrorResponse, inst:
102 except ErrorResponse, inst:
103 req.respond(inst.code, protocol.HGTYPE)
103 req.respond(inst.code, protocol.HGTYPE)
104 if not inst.message:
104 if not inst.message:
105 return []
105 return []
106 return '0\n%s\n' % inst.message,
106 return '0\n%s\n' % inst.message,
107
107
108 # work with CGI variables to create coherent structure
108 # work with CGI variables to create coherent structure
109 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
109 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
110
110
111 req.url = req.env['SCRIPT_NAME']
111 req.url = req.env['SCRIPT_NAME']
112 if not req.url.endswith('/'):
112 if not req.url.endswith('/'):
113 req.url += '/'
113 req.url += '/'
114 if 'REPO_NAME' in req.env:
114 if 'REPO_NAME' in req.env:
115 req.url += req.env['REPO_NAME'] + '/'
115 req.url += req.env['REPO_NAME'] + '/'
116
116
117 if 'PATH_INFO' in req.env:
117 if 'PATH_INFO' in req.env:
118 parts = req.env['PATH_INFO'].strip('/').split('/')
118 parts = req.env['PATH_INFO'].strip('/').split('/')
119 repo_parts = req.env.get('REPO_NAME', '').split('/')
119 repo_parts = req.env.get('REPO_NAME', '').split('/')
120 if parts[:len(repo_parts)] == repo_parts:
120 if parts[:len(repo_parts)] == repo_parts:
121 parts = parts[len(repo_parts):]
121 parts = parts[len(repo_parts):]
122 query = '/'.join(parts)
122 query = '/'.join(parts)
123 else:
123 else:
124 query = req.env['QUERY_STRING'].split('&', 1)[0]
124 query = req.env['QUERY_STRING'].split('&', 1)[0]
125 query = query.split(';', 1)[0]
125 query = query.split(';', 1)[0]
126
126
127 # translate user-visible url structure to internal structure
127 # translate user-visible url structure to internal structure
128
128
129 args = query.split('/', 2)
129 args = query.split('/', 2)
130 if 'cmd' not in req.form and args and args[0]:
130 if 'cmd' not in req.form and args and args[0]:
131
131
132 cmd = args.pop(0)
132 cmd = args.pop(0)
133 style = cmd.rfind('-')
133 style = cmd.rfind('-')
134 if style != -1:
134 if style != -1:
135 req.form['style'] = [cmd[:style]]
135 req.form['style'] = [cmd[:style]]
136 cmd = cmd[style+1:]
136 cmd = cmd[style+1:]
137
137
138 # avoid accepting e.g. style parameter as command
138 # avoid accepting e.g. style parameter as command
139 if hasattr(webcommands, cmd):
139 if hasattr(webcommands, cmd):
140 req.form['cmd'] = [cmd]
140 req.form['cmd'] = [cmd]
141 else:
141 else:
142 cmd = ''
142 cmd = ''
143
143
144 if cmd == 'static':
144 if cmd == 'static':
145 req.form['file'] = ['/'.join(args)]
145 req.form['file'] = ['/'.join(args)]
146 else:
146 else:
147 if args and args[0]:
147 if args and args[0]:
148 node = args.pop(0)
148 node = args.pop(0)
149 req.form['node'] = [node]
149 req.form['node'] = [node]
150 if args:
150 if args:
151 req.form['file'] = args
151 req.form['file'] = args
152
152
153 if cmd == 'archive':
153 if cmd == 'archive':
154 fn = req.form['node'][0]
154 fn = req.form['node'][0]
155 for type_, spec in self.archive_specs.iteritems():
155 for type_, spec in self.archive_specs.iteritems():
156 ext = spec[2]
156 ext = spec[2]
157 if fn.endswith(ext):
157 if fn.endswith(ext):
158 req.form['node'] = [fn[:-len(ext)]]
158 req.form['node'] = [fn[:-len(ext)]]
159 req.form['type'] = [type_]
159 req.form['type'] = [type_]
160
160
161 # process the web interface request
161 # process the web interface request
162
162
163 try:
163 try:
164 tmpl = self.templater(req)
164 tmpl = self.templater(req)
165 ctype = tmpl('mimetype', encoding=self.encoding)
165 ctype = tmpl('mimetype', encoding=self.encoding)
166 ctype = templater.stringify(ctype)
166 ctype = templater.stringify(ctype)
167
167
168 # check allow_read / deny_read config options
168 # check allow_read / deny_read config options
169 self.check_perm(req, None)
169 self.check_perm(req, None)
170
170
171 if cmd == '':
171 if cmd == '':
172 req.form['cmd'] = [tmpl.cache['default']]
172 req.form['cmd'] = [tmpl.cache['default']]
173 cmd = req.form['cmd'][0]
173 cmd = req.form['cmd'][0]
174
174
175 if cmd not in webcommands.__all__:
175 if cmd not in webcommands.__all__:
176 msg = 'no such method: %s' % cmd
176 msg = 'no such method: %s' % cmd
177 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
177 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
178 elif cmd == 'file' and 'raw' in req.form.get('style', []):
178 elif cmd == 'file' and 'raw' in req.form.get('style', []):
179 self.ctype = ctype
179 self.ctype = ctype
180 content = webcommands.rawfile(self, req, tmpl)
180 content = webcommands.rawfile(self, req, tmpl)
181 else:
181 else:
182 content = getattr(webcommands, cmd)(self, req, tmpl)
182 content = getattr(webcommands, cmd)(self, req, tmpl)
183 req.respond(HTTP_OK, ctype)
183 req.respond(HTTP_OK, ctype)
184
184
185 return ''.join(content),
185 return content
186
186
187 except revlog.LookupError, err:
187 except revlog.LookupError, err:
188 req.respond(HTTP_NOT_FOUND, ctype)
188 req.respond(HTTP_NOT_FOUND, ctype)
189 msg = str(err)
189 msg = str(err)
190 if 'manifest' not in msg:
190 if 'manifest' not in msg:
191 msg = 'revision not found: %s' % err.name
191 msg = 'revision not found: %s' % err.name
192 return ''.join(tmpl('error', error=msg)),
192 return tmpl('error', error=msg)
193 except (RepoError, revlog.RevlogError), inst:
193 except (RepoError, revlog.RevlogError), inst:
194 req.respond(HTTP_SERVER_ERROR, ctype)
194 req.respond(HTTP_SERVER_ERROR, ctype)
195 return ''.join(tmpl('error', error=str(inst))),
195 return tmpl('error', error=str(inst))
196 except ErrorResponse, inst:
196 except ErrorResponse, inst:
197 req.respond(inst.code, ctype)
197 req.respond(inst.code, ctype)
198 return ''.join(tmpl('error', error=inst.message)),
198 return tmpl('error', error=inst.message)
199
199
200 def templater(self, req):
200 def templater(self, req):
201
201
202 # determine scheme, port and server name
202 # determine scheme, port and server name
203 # this is needed to create absolute urls
203 # this is needed to create absolute urls
204
204
205 proto = req.env.get('wsgi.url_scheme')
205 proto = req.env.get('wsgi.url_scheme')
206 if proto == 'https':
206 if proto == 'https':
207 proto = 'https'
207 proto = 'https'
208 default_port = "443"
208 default_port = "443"
209 else:
209 else:
210 proto = 'http'
210 proto = 'http'
211 default_port = "80"
211 default_port = "80"
212
212
213 port = req.env["SERVER_PORT"]
213 port = req.env["SERVER_PORT"]
214 port = port != default_port and (":" + port) or ""
214 port = port != default_port and (":" + port) or ""
215 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
215 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
216 staticurl = self.config("web", "staticurl") or req.url + 'static/'
216 staticurl = self.config("web", "staticurl") or req.url + 'static/'
217 if not staticurl.endswith('/'):
217 if not staticurl.endswith('/'):
218 staticurl += '/'
218 staticurl += '/'
219
219
220 # some functions for the templater
220 # some functions for the templater
221
221
222 def header(**map):
222 def header(**map):
223 yield tmpl('header', encoding=self.encoding, **map)
223 yield tmpl('header', encoding=self.encoding, **map)
224
224
225 def footer(**map):
225 def footer(**map):
226 yield tmpl("footer", **map)
226 yield tmpl("footer", **map)
227
227
228 def motd(**map):
228 def motd(**map):
229 yield self.config("web", "motd", "")
229 yield self.config("web", "motd", "")
230
230
231 # figure out which style to use
231 # figure out which style to use
232
232
233 vars = {}
233 vars = {}
234 style = self.config("web", "style", "paper")
234 style = self.config("web", "style", "paper")
235 if 'style' in req.form:
235 if 'style' in req.form:
236 style = req.form['style'][0]
236 style = req.form['style'][0]
237 vars['style'] = style
237 vars['style'] = style
238
238
239 start = req.url[-1] == '?' and '&' or '?'
239 start = req.url[-1] == '?' and '&' or '?'
240 sessionvars = webutil.sessionvars(vars, start)
240 sessionvars = webutil.sessionvars(vars, start)
241 mapfile = style_map(self.templatepath, style)
241 mapfile = style_map(self.templatepath, style)
242
242
243 if not self.reponame:
243 if not self.reponame:
244 self.reponame = (self.config("web", "name")
244 self.reponame = (self.config("web", "name")
245 or req.env.get('REPO_NAME')
245 or req.env.get('REPO_NAME')
246 or req.url.strip('/') or self.repo.root)
246 or req.url.strip('/') or self.repo.root)
247
247
248 # create the templater
248 # create the templater
249
249
250 tmpl = templater.templater(mapfile, templatefilters.filters,
250 tmpl = templater.templater(mapfile, templatefilters.filters,
251 defaults={"url": req.url,
251 defaults={"url": req.url,
252 "staticurl": staticurl,
252 "staticurl": staticurl,
253 "urlbase": urlbase,
253 "urlbase": urlbase,
254 "repo": self.reponame,
254 "repo": self.reponame,
255 "header": header,
255 "header": header,
256 "footer": footer,
256 "footer": footer,
257 "motd": motd,
257 "motd": motd,
258 "sessionvars": sessionvars
258 "sessionvars": sessionvars
259 })
259 })
260 return tmpl
260 return tmpl
261
261
262 def archivelist(self, nodeid):
262 def archivelist(self, nodeid):
263 allowed = self.configlist("web", "allow_archive")
263 allowed = self.configlist("web", "allow_archive")
264 for i, spec in self.archive_specs.iteritems():
264 for i, spec in self.archive_specs.iteritems():
265 if i in allowed or self.configbool("web", "allow" + i):
265 if i in allowed or self.configbool("web", "allow" + i):
266 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
266 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
267
267
268 archive_specs = {
268 archive_specs = {
269 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
269 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
270 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
270 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
271 'zip': ('application/zip', 'zip', '.zip', None),
271 'zip': ('application/zip', 'zip', '.zip', None),
272 }
272 }
273
273
274 def check_perm(self, req, op):
274 def check_perm(self, req, op):
275 '''Check permission for operation based on request data (including
275 '''Check permission for operation based on request data (including
276 authentication info). Return if op allowed, else raise an ErrorResponse
276 authentication info). Return if op allowed, else raise an ErrorResponse
277 exception.'''
277 exception.'''
278
278
279 user = req.env.get('REMOTE_USER')
279 user = req.env.get('REMOTE_USER')
280
280
281 deny_read = self.configlist('web', 'deny_read')
281 deny_read = self.configlist('web', 'deny_read')
282 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
282 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
283 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
283 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
284
284
285 allow_read = self.configlist('web', 'allow_read')
285 allow_read = self.configlist('web', 'allow_read')
286 result = (not allow_read) or (allow_read == ['*']) or (user in allow_read)
286 result = (not allow_read) or (allow_read == ['*']) or (user in allow_read)
287 if not result:
287 if not result:
288 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
288 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
289
289
290 if op == 'pull' and not self.allowpull:
290 if op == 'pull' and not self.allowpull:
291 raise ErrorResponse(HTTP_OK, '')
291 raise ErrorResponse(HTTP_OK, '')
292 # op is None when checking allow/deny_read permissions for a web-browser request
292 # op is None when checking allow/deny_read permissions for a web-browser request
293 elif op == 'pull' or op is None:
293 elif op == 'pull' or op is None:
294 return
294 return
295
295
296 # enforce that you can only push using POST requests
296 # enforce that you can only push using POST requests
297 if req.env['REQUEST_METHOD'] != 'POST':
297 if req.env['REQUEST_METHOD'] != 'POST':
298 msg = 'push requires POST request'
298 msg = 'push requires POST request'
299 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
299 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
300
300
301 # require ssl by default for pushing, auth info cannot be sniffed
301 # require ssl by default for pushing, auth info cannot be sniffed
302 # and replayed
302 # and replayed
303 scheme = req.env.get('wsgi.url_scheme')
303 scheme = req.env.get('wsgi.url_scheme')
304 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
304 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
305 raise ErrorResponse(HTTP_OK, 'ssl required')
305 raise ErrorResponse(HTTP_OK, 'ssl required')
306
306
307 deny = self.configlist('web', 'deny_push')
307 deny = self.configlist('web', 'deny_push')
308 if deny and (not user or deny == ['*'] or user in deny):
308 if deny and (not user or deny == ['*'] or user in deny):
309 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
309 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
310
310
311 allow = self.configlist('web', 'allow_push')
311 allow = self.configlist('web', 'allow_push')
312 result = allow and (allow == ['*'] or user in allow)
312 result = allow and (allow == ['*'] or user in allow)
313 if not result:
313 if not result:
314 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
314 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
@@ -1,309 +1,309 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os
9 import os
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import ui, hg, util, templater, templatefilters
12 from mercurial import ui, hg, util, templater, templatefilters
13 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
13 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
16 from request import wsgirequest
16 from request import wsgirequest
17
17
18 # This is a stopgap
18 # This is a stopgap
19 class hgwebdir(object):
19 class hgwebdir(object):
20 def __init__(self, config, parentui=None):
20 def __init__(self, config, parentui=None):
21 def cleannames(items):
21 def cleannames(items):
22 return [(util.pconvert(name).strip('/'), path)
22 return [(util.pconvert(name).strip('/'), path)
23 for name, path in items]
23 for name, path in items]
24
24
25 self.parentui = parentui or ui.ui(report_untrusted=False,
25 self.parentui = parentui or ui.ui(report_untrusted=False,
26 interactive = False)
26 interactive = False)
27 self.motd = None
27 self.motd = None
28 self.style = 'paper'
28 self.style = 'paper'
29 self.stripecount = None
29 self.stripecount = None
30 self.repos_sorted = ('name', False)
30 self.repos_sorted = ('name', False)
31 self._baseurl = None
31 self._baseurl = None
32 if isinstance(config, (list, tuple)):
32 if isinstance(config, (list, tuple)):
33 self.repos = cleannames(config)
33 self.repos = cleannames(config)
34 self.repos_sorted = ('', False)
34 self.repos_sorted = ('', False)
35 elif isinstance(config, dict):
35 elif isinstance(config, dict):
36 self.repos = util.sort(cleannames(config.items()))
36 self.repos = util.sort(cleannames(config.items()))
37 else:
37 else:
38 if isinstance(config, util.configparser):
38 if isinstance(config, util.configparser):
39 cp = config
39 cp = config
40 else:
40 else:
41 cp = util.configparser()
41 cp = util.configparser()
42 cp.read(config)
42 cp.read(config)
43 self.repos = []
43 self.repos = []
44 if cp.has_section('web'):
44 if cp.has_section('web'):
45 if cp.has_option('web', 'motd'):
45 if cp.has_option('web', 'motd'):
46 self.motd = cp.get('web', 'motd')
46 self.motd = cp.get('web', 'motd')
47 if cp.has_option('web', 'style'):
47 if cp.has_option('web', 'style'):
48 self.style = cp.get('web', 'style')
48 self.style = cp.get('web', 'style')
49 if cp.has_option('web', 'stripes'):
49 if cp.has_option('web', 'stripes'):
50 self.stripecount = int(cp.get('web', 'stripes'))
50 self.stripecount = int(cp.get('web', 'stripes'))
51 if cp.has_option('web', 'baseurl'):
51 if cp.has_option('web', 'baseurl'):
52 self._baseurl = cp.get('web', 'baseurl')
52 self._baseurl = cp.get('web', 'baseurl')
53 if cp.has_section('paths'):
53 if cp.has_section('paths'):
54 self.repos.extend(cleannames(cp.items('paths')))
54 self.repos.extend(cleannames(cp.items('paths')))
55 if cp.has_section('collections'):
55 if cp.has_section('collections'):
56 for prefix, root in cp.items('collections'):
56 for prefix, root in cp.items('collections'):
57 for path in util.walkrepos(root, followsym=True):
57 for path in util.walkrepos(root, followsym=True):
58 repo = os.path.normpath(path)
58 repo = os.path.normpath(path)
59 name = repo
59 name = repo
60 if name.startswith(prefix):
60 if name.startswith(prefix):
61 name = name[len(prefix):]
61 name = name[len(prefix):]
62 self.repos.append((name.lstrip(os.sep), repo))
62 self.repos.append((name.lstrip(os.sep), repo))
63 self.repos.sort()
63 self.repos.sort()
64
64
65 def run(self):
65 def run(self):
66 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
66 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
67 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
67 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
68 import mercurial.hgweb.wsgicgi as wsgicgi
68 import mercurial.hgweb.wsgicgi as wsgicgi
69 wsgicgi.launch(self)
69 wsgicgi.launch(self)
70
70
71 def __call__(self, env, respond):
71 def __call__(self, env, respond):
72 req = wsgirequest(env, respond)
72 req = wsgirequest(env, respond)
73 return self.run_wsgi(req)
73 return self.run_wsgi(req)
74
74
75 def read_allowed(self, ui, req):
75 def read_allowed(self, ui, req):
76 """Check allow_read and deny_read config options of a repo's ui object
76 """Check allow_read and deny_read config options of a repo's ui object
77 to determine user permissions. By default, with neither option set (or
77 to determine user permissions. By default, with neither option set (or
78 both empty), allow all users to read the repo. There are two ways a
78 both empty), allow all users to read the repo. There are two ways a
79 user can be denied read access: (1) deny_read is not empty, and the
79 user can be denied read access: (1) deny_read is not empty, and the
80 user is unauthenticated or deny_read contains user (or *), and (2)
80 user is unauthenticated or deny_read contains user (or *), and (2)
81 allow_read is not empty and the user is not in allow_read. Return True
81 allow_read is not empty and the user is not in allow_read. Return True
82 if user is allowed to read the repo, else return False."""
82 if user is allowed to read the repo, else return False."""
83
83
84 user = req.env.get('REMOTE_USER')
84 user = req.env.get('REMOTE_USER')
85
85
86 deny_read = ui.configlist('web', 'deny_read', default=None, untrusted=True)
86 deny_read = ui.configlist('web', 'deny_read', default=None, untrusted=True)
87 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
87 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
88 return False
88 return False
89
89
90 allow_read = ui.configlist('web', 'allow_read', default=None, untrusted=True)
90 allow_read = ui.configlist('web', 'allow_read', default=None, untrusted=True)
91 # by default, allow reading if no allow_read option has been set
91 # by default, allow reading if no allow_read option has been set
92 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
92 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
93 return True
93 return True
94
94
95 return False
95 return False
96
96
97 def run_wsgi(self, req):
97 def run_wsgi(self, req):
98
98
99 try:
99 try:
100 try:
100 try:
101
101
102 virtual = req.env.get("PATH_INFO", "").strip('/')
102 virtual = req.env.get("PATH_INFO", "").strip('/')
103 tmpl = self.templater(req)
103 tmpl = self.templater(req)
104 ctype = tmpl('mimetype', encoding=util._encoding)
104 ctype = tmpl('mimetype', encoding=util._encoding)
105 ctype = templater.stringify(ctype)
105 ctype = templater.stringify(ctype)
106
106
107 # a static file
107 # a static file
108 if virtual.startswith('static/') or 'static' in req.form:
108 if virtual.startswith('static/') or 'static' in req.form:
109 if virtual.startswith('static/'):
109 if virtual.startswith('static/'):
110 fname = virtual[7:]
110 fname = virtual[7:]
111 else:
111 else:
112 fname = req.form['static'][0]
112 fname = req.form['static'][0]
113 static = templater.templatepath('static')
113 static = templater.templatepath('static')
114 return staticfile(static, fname, req)
114 return staticfile(static, fname, req)
115
115
116 # top-level index
116 # top-level index
117 elif not virtual:
117 elif not virtual:
118 req.respond(HTTP_OK, ctype)
118 req.respond(HTTP_OK, ctype)
119 return ''.join(self.makeindex(req, tmpl)),
119 return self.makeindex(req, tmpl)
120
120
121 # nested indexes and hgwebs
121 # nested indexes and hgwebs
122
122
123 repos = dict(self.repos)
123 repos = dict(self.repos)
124 while virtual:
124 while virtual:
125 real = repos.get(virtual)
125 real = repos.get(virtual)
126 if real:
126 if real:
127 req.env['REPO_NAME'] = virtual
127 req.env['REPO_NAME'] = virtual
128 try:
128 try:
129 repo = hg.repository(self.parentui, real)
129 repo = hg.repository(self.parentui, real)
130 return hgweb(repo).run_wsgi(req)
130 return hgweb(repo).run_wsgi(req)
131 except IOError, inst:
131 except IOError, inst:
132 msg = inst.strerror
132 msg = inst.strerror
133 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
133 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
134 except RepoError, inst:
134 except RepoError, inst:
135 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
135 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
136
136
137 # browse subdirectories
137 # browse subdirectories
138 subdir = virtual + '/'
138 subdir = virtual + '/'
139 if [r for r in repos if r.startswith(subdir)]:
139 if [r for r in repos if r.startswith(subdir)]:
140 req.respond(HTTP_OK, ctype)
140 req.respond(HTTP_OK, ctype)
141 return ''.join(self.makeindex(req, tmpl, subdir)),
141 return self.makeindex(req, tmpl, subdir)
142
142
143 up = virtual.rfind('/')
143 up = virtual.rfind('/')
144 if up < 0:
144 if up < 0:
145 break
145 break
146 virtual = virtual[:up]
146 virtual = virtual[:up]
147
147
148 # prefixes not found
148 # prefixes not found
149 req.respond(HTTP_NOT_FOUND, ctype)
149 req.respond(HTTP_NOT_FOUND, ctype)
150 return ''.join(tmpl("notfound", repo=virtual)),
150 return tmpl("notfound", repo=virtual)
151
151
152 except ErrorResponse, err:
152 except ErrorResponse, err:
153 req.respond(err.code, ctype)
153 req.respond(err.code, ctype)
154 return ''.join(tmpl('error', error=err.message or '')),
154 return tmpl('error', error=err.message or '')
155 finally:
155 finally:
156 tmpl = None
156 tmpl = None
157
157
158 def makeindex(self, req, tmpl, subdir=""):
158 def makeindex(self, req, tmpl, subdir=""):
159
159
160 def archivelist(ui, nodeid, url):
160 def archivelist(ui, nodeid, url):
161 allowed = ui.configlist("web", "allow_archive", untrusted=True)
161 allowed = ui.configlist("web", "allow_archive", untrusted=True)
162 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
162 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
163 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
163 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
164 untrusted=True):
164 untrusted=True):
165 yield {"type" : i[0], "extension": i[1],
165 yield {"type" : i[0], "extension": i[1],
166 "node": nodeid, "url": url}
166 "node": nodeid, "url": url}
167
167
168 def entries(sortcolumn="", descending=False, subdir="", **map):
168 def entries(sortcolumn="", descending=False, subdir="", **map):
169 def sessionvars(**map):
169 def sessionvars(**map):
170 fields = []
170 fields = []
171 if 'style' in req.form:
171 if 'style' in req.form:
172 style = req.form['style'][0]
172 style = req.form['style'][0]
173 if style != get('web', 'style', ''):
173 if style != get('web', 'style', ''):
174 fields.append(('style', style))
174 fields.append(('style', style))
175
175
176 separator = url[-1] == '?' and ';' or '?'
176 separator = url[-1] == '?' and ';' or '?'
177 for name, value in fields:
177 for name, value in fields:
178 yield dict(name=name, value=value, separator=separator)
178 yield dict(name=name, value=value, separator=separator)
179 separator = ';'
179 separator = ';'
180
180
181 rows = []
181 rows = []
182 parity = paritygen(self.stripecount)
182 parity = paritygen(self.stripecount)
183 for name, path in self.repos:
183 for name, path in self.repos:
184 if not name.startswith(subdir):
184 if not name.startswith(subdir):
185 continue
185 continue
186 name = name[len(subdir):]
186 name = name[len(subdir):]
187
187
188 u = ui.ui(parentui=self.parentui)
188 u = ui.ui(parentui=self.parentui)
189 try:
189 try:
190 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
190 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
191 except Exception, e:
191 except Exception, e:
192 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
192 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
193 continue
193 continue
194 def get(section, name, default=None):
194 def get(section, name, default=None):
195 return u.config(section, name, default, untrusted=True)
195 return u.config(section, name, default, untrusted=True)
196
196
197 if u.configbool("web", "hidden", untrusted=True):
197 if u.configbool("web", "hidden", untrusted=True):
198 continue
198 continue
199
199
200 if not self.read_allowed(u, req):
200 if not self.read_allowed(u, req):
201 continue
201 continue
202
202
203 parts = [name]
203 parts = [name]
204 if 'PATH_INFO' in req.env:
204 if 'PATH_INFO' in req.env:
205 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
205 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
206 if req.env['SCRIPT_NAME']:
206 if req.env['SCRIPT_NAME']:
207 parts.insert(0, req.env['SCRIPT_NAME'])
207 parts.insert(0, req.env['SCRIPT_NAME'])
208 url = ('/'.join(parts).replace("//", "/")) + '/'
208 url = ('/'.join(parts).replace("//", "/")) + '/'
209
209
210 # update time with local timezone
210 # update time with local timezone
211 try:
211 try:
212 d = (get_mtime(path), util.makedate()[1])
212 d = (get_mtime(path), util.makedate()[1])
213 except OSError:
213 except OSError:
214 continue
214 continue
215
215
216 contact = get_contact(get)
216 contact = get_contact(get)
217 description = get("web", "description", "")
217 description = get("web", "description", "")
218 name = get("web", "name", name)
218 name = get("web", "name", name)
219 row = dict(contact=contact or "unknown",
219 row = dict(contact=contact or "unknown",
220 contact_sort=contact.upper() or "unknown",
220 contact_sort=contact.upper() or "unknown",
221 name=name,
221 name=name,
222 name_sort=name,
222 name_sort=name,
223 url=url,
223 url=url,
224 description=description or "unknown",
224 description=description or "unknown",
225 description_sort=description.upper() or "unknown",
225 description_sort=description.upper() or "unknown",
226 lastchange=d,
226 lastchange=d,
227 lastchange_sort=d[1]-d[0],
227 lastchange_sort=d[1]-d[0],
228 sessionvars=sessionvars,
228 sessionvars=sessionvars,
229 archives=archivelist(u, "tip", url))
229 archives=archivelist(u, "tip", url))
230 if (not sortcolumn
230 if (not sortcolumn
231 or (sortcolumn, descending) == self.repos_sorted):
231 or (sortcolumn, descending) == self.repos_sorted):
232 # fast path for unsorted output
232 # fast path for unsorted output
233 row['parity'] = parity.next()
233 row['parity'] = parity.next()
234 yield row
234 yield row
235 else:
235 else:
236 rows.append((row["%s_sort" % sortcolumn], row))
236 rows.append((row["%s_sort" % sortcolumn], row))
237 if rows:
237 if rows:
238 rows.sort()
238 rows.sort()
239 if descending:
239 if descending:
240 rows.reverse()
240 rows.reverse()
241 for key, row in rows:
241 for key, row in rows:
242 row['parity'] = parity.next()
242 row['parity'] = parity.next()
243 yield row
243 yield row
244
244
245 sortable = ["name", "description", "contact", "lastchange"]
245 sortable = ["name", "description", "contact", "lastchange"]
246 sortcolumn, descending = self.repos_sorted
246 sortcolumn, descending = self.repos_sorted
247 if 'sort' in req.form:
247 if 'sort' in req.form:
248 sortcolumn = req.form['sort'][0]
248 sortcolumn = req.form['sort'][0]
249 descending = sortcolumn.startswith('-')
249 descending = sortcolumn.startswith('-')
250 if descending:
250 if descending:
251 sortcolumn = sortcolumn[1:]
251 sortcolumn = sortcolumn[1:]
252 if sortcolumn not in sortable:
252 if sortcolumn not in sortable:
253 sortcolumn = ""
253 sortcolumn = ""
254
254
255 sort = [("sort_%s" % column,
255 sort = [("sort_%s" % column,
256 "%s%s" % ((not descending and column == sortcolumn)
256 "%s%s" % ((not descending and column == sortcolumn)
257 and "-" or "", column))
257 and "-" or "", column))
258 for column in sortable]
258 for column in sortable]
259
259
260 if self._baseurl is not None:
260 if self._baseurl is not None:
261 req.env['SCRIPT_NAME'] = self._baseurl
261 req.env['SCRIPT_NAME'] = self._baseurl
262
262
263 return tmpl("index", entries=entries, subdir=subdir,
263 return tmpl("index", entries=entries, subdir=subdir,
264 sortcolumn=sortcolumn, descending=descending,
264 sortcolumn=sortcolumn, descending=descending,
265 **dict(sort))
265 **dict(sort))
266
266
267 def templater(self, req):
267 def templater(self, req):
268
268
269 def header(**map):
269 def header(**map):
270 yield tmpl('header', encoding=util._encoding, **map)
270 yield tmpl('header', encoding=util._encoding, **map)
271
271
272 def footer(**map):
272 def footer(**map):
273 yield tmpl("footer", **map)
273 yield tmpl("footer", **map)
274
274
275 def motd(**map):
275 def motd(**map):
276 if self.motd is not None:
276 if self.motd is not None:
277 yield self.motd
277 yield self.motd
278 else:
278 else:
279 yield config('web', 'motd', '')
279 yield config('web', 'motd', '')
280
280
281 def config(section, name, default=None, untrusted=True):
281 def config(section, name, default=None, untrusted=True):
282 return self.parentui.config(section, name, default, untrusted)
282 return self.parentui.config(section, name, default, untrusted)
283
283
284 if self._baseurl is not None:
284 if self._baseurl is not None:
285 req.env['SCRIPT_NAME'] = self._baseurl
285 req.env['SCRIPT_NAME'] = self._baseurl
286
286
287 url = req.env.get('SCRIPT_NAME', '')
287 url = req.env.get('SCRIPT_NAME', '')
288 if not url.endswith('/'):
288 if not url.endswith('/'):
289 url += '/'
289 url += '/'
290
290
291 staticurl = config('web', 'staticurl') or url + 'static/'
291 staticurl = config('web', 'staticurl') or url + 'static/'
292 if not staticurl.endswith('/'):
292 if not staticurl.endswith('/'):
293 staticurl += '/'
293 staticurl += '/'
294
294
295 style = self.style
295 style = self.style
296 if style is None:
296 if style is None:
297 style = config('web', 'style', '')
297 style = config('web', 'style', '')
298 if 'style' in req.form:
298 if 'style' in req.form:
299 style = req.form['style'][0]
299 style = req.form['style'][0]
300 if self.stripecount is None:
300 if self.stripecount is None:
301 self.stripecount = int(config('web', 'stripes', 1))
301 self.stripecount = int(config('web', 'stripes', 1))
302 mapfile = style_map(templater.templatepath(), style)
302 mapfile = style_map(templater.templatepath(), style)
303 tmpl = templater.templater(mapfile, templatefilters.filters,
303 tmpl = templater.templater(mapfile, templatefilters.filters,
304 defaults={"header": header,
304 defaults={"header": header,
305 "footer": footer,
305 "footer": footer,
306 "motd": motd,
306 "motd": motd,
307 "url": url,
307 "url": url,
308 "staticurl": staticurl})
308 "staticurl": staticurl})
309 return tmpl
309 return tmpl
@@ -1,182 +1,191 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import re, sys, os
9 import re, sys, os
10 from mercurial import util
10 from mercurial import util
11
11
12 path = ['templates', '../templates']
12 path = ['templates', '../templates']
13
13
14 def parsestring(s, quoted=True):
14 def parsestring(s, quoted=True):
15 '''parse a string using simple c-like syntax.
15 '''parse a string using simple c-like syntax.
16 string must be in quotes if quoted is True.'''
16 string must be in quotes if quoted is True.'''
17 if quoted:
17 if quoted:
18 if len(s) < 2 or s[0] != s[-1]:
18 if len(s) < 2 or s[0] != s[-1]:
19 raise SyntaxError(_('unmatched quotes'))
19 raise SyntaxError(_('unmatched quotes'))
20 return s[1:-1].decode('string_escape')
20 return s[1:-1].decode('string_escape')
21
21
22 return s.decode('string_escape')
22 return s.decode('string_escape')
23
23
24 class templater(object):
24 class templater(object):
25 '''template expansion engine.
25 '''template expansion engine.
26
26
27 template expansion works like this. a map file contains key=value
27 template expansion works like this. a map file contains key=value
28 pairs. if value is quoted, it is treated as string. otherwise, it
28 pairs. if value is quoted, it is treated as string. otherwise, it
29 is treated as name of template file.
29 is treated as name of template file.
30
30
31 templater is asked to expand a key in map. it looks up key, and
31 templater is asked to expand a key in map. it looks up key, and
32 looks for strings like this: {foo}. it expands {foo} by looking up
32 looks for strings like this: {foo}. it expands {foo} by looking up
33 foo in map, and substituting it. expansion is recursive: it stops
33 foo in map, and substituting it. expansion is recursive: it stops
34 when there is no more {foo} to replace.
34 when there is no more {foo} to replace.
35
35
36 expansion also allows formatting and filtering.
36 expansion also allows formatting and filtering.
37
37
38 format uses key to expand each item in list. syntax is
38 format uses key to expand each item in list. syntax is
39 {key%format}.
39 {key%format}.
40
40
41 filter uses function to transform value. syntax is
41 filter uses function to transform value. syntax is
42 {key|filter1|filter2|...}.'''
42 {key|filter1|filter2|...}.'''
43
43
44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
44 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
45 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
46
46
47 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
47 def __init__(self, mapfile, filters={}, defaults={}, cache={},
48 minchunk=1024, maxchunk=65536):
48 '''set up template engine.
49 '''set up template engine.
49 mapfile is name of file to read map definitions from.
50 mapfile is name of file to read map definitions from.
50 filters is dict of functions. each transforms a value into another.
51 filters is dict of functions. each transforms a value into another.
51 defaults is dict of default map definitions.'''
52 defaults is dict of default map definitions.'''
52 self.mapfile = mapfile or 'template'
53 self.mapfile = mapfile or 'template'
53 self.cache = cache.copy()
54 self.cache = cache.copy()
54 self.map = {}
55 self.map = {}
55 self.base = (mapfile and os.path.dirname(mapfile)) or ''
56 self.base = (mapfile and os.path.dirname(mapfile)) or ''
56 self.filters = filters
57 self.filters = filters
57 self.defaults = defaults
58 self.defaults = defaults
59 self.minchunk, self.maxchunk = minchunk, maxchunk
58
60
59 if not mapfile:
61 if not mapfile:
60 return
62 return
61 if not os.path.exists(mapfile):
63 if not os.path.exists(mapfile):
62 raise util.Abort(_('style not found: %s') % mapfile)
64 raise util.Abort(_('style not found: %s') % mapfile)
63
65
64 i = 0
66 i = 0
65 for l in file(mapfile):
67 for l in file(mapfile):
66 l = l.strip()
68 l = l.strip()
67 i += 1
69 i += 1
68 if not l or l[0] in '#;': continue
70 if not l or l[0] in '#;': continue
69 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
71 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
70 if m:
72 if m:
71 key, val = m.groups()
73 key, val = m.groups()
72 if val[0] in "'\"":
74 if val[0] in "'\"":
73 try:
75 try:
74 self.cache[key] = parsestring(val)
76 self.cache[key] = parsestring(val)
75 except SyntaxError, inst:
77 except SyntaxError, inst:
76 raise SyntaxError('%s:%s: %s' %
78 raise SyntaxError('%s:%s: %s' %
77 (mapfile, i, inst.args[0]))
79 (mapfile, i, inst.args[0]))
78 else:
80 else:
79 self.map[key] = os.path.join(self.base, val)
81 self.map[key] = os.path.join(self.base, val)
80 else:
82 else:
81 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
83 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
82
84
83 def __contains__(self, key):
85 def __contains__(self, key):
84 return key in self.cache or key in self.map
86 return key in self.cache or key in self.map
85
87
86 def _template(self, t):
88 def _template(self, t):
87 '''Get the template for the given template name. Use a local cache.'''
89 '''Get the template for the given template name. Use a local cache.'''
88 if not t in self.cache:
90 if not t in self.cache:
89 try:
91 try:
90 self.cache[t] = file(self.map[t]).read()
92 self.cache[t] = file(self.map[t]).read()
91 except IOError, inst:
93 except IOError, inst:
92 raise IOError(inst.args[0], _('template file %s: %s') %
94 raise IOError(inst.args[0], _('template file %s: %s') %
93 (self.map[t], inst.args[1]))
95 (self.map[t], inst.args[1]))
94 return self.cache[t]
96 return self.cache[t]
95
97
96 def _process(self, tmpl, map):
98 def _process(self, tmpl, map):
97 '''Render a template. Returns a generator.'''
99 '''Render a template. Returns a generator.'''
98 while tmpl:
100 while tmpl:
99 m = self.template_re.search(tmpl)
101 m = self.template_re.search(tmpl)
100 if not m:
102 if not m:
101 yield tmpl
103 yield tmpl
102 break
104 break
103
105
104 start, end = m.span(0)
106 start, end = m.span(0)
105 key, format, fl = m.groups()
107 key, format, fl = m.groups()
106
108
107 if start:
109 if start:
108 yield tmpl[:start]
110 yield tmpl[:start]
109 tmpl = tmpl[end:]
111 tmpl = tmpl[end:]
110
112
111 if key in map:
113 if key in map:
112 v = map[key]
114 v = map[key]
113 else:
115 else:
114 v = self.defaults.get(key, "")
116 v = self.defaults.get(key, "")
115 if callable(v):
117 if callable(v):
116 v = v(**map)
118 v = v(**map)
117 if format:
119 if format:
118 if not hasattr(v, '__iter__'):
120 if not hasattr(v, '__iter__'):
119 raise SyntaxError(_("Error expanding '%s%%%s'")
121 raise SyntaxError(_("Error expanding '%s%%%s'")
120 % (key, format))
122 % (key, format))
121 lm = map.copy()
123 lm = map.copy()
122 for i in v:
124 for i in v:
123 lm.update(i)
125 lm.update(i)
124 t = self._template(format)
126 t = self._template(format)
125 yield self._process(t, lm)
127 yield self._process(t, lm)
126 else:
128 else:
127 if fl:
129 if fl:
128 for f in fl.split("|")[1:]:
130 for f in fl.split("|")[1:]:
129 v = self.filters[f](v)
131 v = self.filters[f](v)
130 yield v
132 yield v
131
133
132 def __call__(self, t, **map):
134 def __call__(self, t, **map):
135 stream = self.expand(t, **map)
136 if self.minchunk:
137 stream = util.increasingchunks(stream, min=self.minchunk,
138 max=self.maxchunk)
139 return stream
140
141 def expand(self, t, **map):
133 '''Perform expansion. t is name of map element to expand. map contains
142 '''Perform expansion. t is name of map element to expand. map contains
134 added elements for use during expansion. Is a generator.'''
143 added elements for use during expansion. Is a generator.'''
135 tmpl = self._template(t)
144 tmpl = self._template(t)
136 iters = [self._process(tmpl, map)]
145 iters = [self._process(tmpl, map)]
137 while iters:
146 while iters:
138 try:
147 try:
139 item = iters[0].next()
148 item = iters[0].next()
140 except StopIteration:
149 except StopIteration:
141 iters.pop(0)
150 iters.pop(0)
142 continue
151 continue
143 if isinstance(item, str):
152 if isinstance(item, str):
144 yield item
153 yield item
145 elif item is None:
154 elif item is None:
146 yield ''
155 yield ''
147 elif hasattr(item, '__iter__'):
156 elif hasattr(item, '__iter__'):
148 iters.insert(0, iter(item))
157 iters.insert(0, iter(item))
149 else:
158 else:
150 yield str(item)
159 yield str(item)
151
160
152 def templatepath(name=None):
161 def templatepath(name=None):
153 '''return location of template file or directory (if no name).
162 '''return location of template file or directory (if no name).
154 returns None if not found.'''
163 returns None if not found.'''
155 normpaths = []
164 normpaths = []
156
165
157 # executable version (py2exe) doesn't support __file__
166 # executable version (py2exe) doesn't support __file__
158 if hasattr(sys, 'frozen'):
167 if hasattr(sys, 'frozen'):
159 module = sys.executable
168 module = sys.executable
160 else:
169 else:
161 module = __file__
170 module = __file__
162 for f in path:
171 for f in path:
163 if f.startswith('/'):
172 if f.startswith('/'):
164 p = f
173 p = f
165 else:
174 else:
166 fl = f.split('/')
175 fl = f.split('/')
167 p = os.path.join(os.path.dirname(module), *fl)
176 p = os.path.join(os.path.dirname(module), *fl)
168 if name:
177 if name:
169 p = os.path.join(p, name)
178 p = os.path.join(p, name)
170 if name and os.path.exists(p):
179 if name and os.path.exists(p):
171 return os.path.normpath(p)
180 return os.path.normpath(p)
172 elif os.path.isdir(p):
181 elif os.path.isdir(p):
173 normpaths.append(os.path.normpath(p))
182 normpaths.append(os.path.normpath(p))
174
183
175 return normpaths
184 return normpaths
176
185
177 def stringify(thing):
186 def stringify(thing):
178 '''turn nested template iterator into string.'''
187 '''turn nested template iterator into string.'''
179 if hasattr(thing, '__iter__'):
188 if hasattr(thing, '__iter__'):
180 return "".join([stringify(t) for t in thing if t is not None])
189 return "".join([stringify(t) for t in thing if t is not None])
181 return str(thing)
190 return str(thing)
182
191
@@ -1,1934 +1,1965 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile
16 import cStringIO, errno, getpass, re, shutil, sys, tempfile
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 import imp
18 import imp
19
19
20 # Python compatibility
20 # Python compatibility
21
21
22 try:
22 try:
23 set = set
23 set = set
24 frozenset = frozenset
24 frozenset = frozenset
25 except NameError:
25 except NameError:
26 from sets import Set as set, ImmutableSet as frozenset
26 from sets import Set as set, ImmutableSet as frozenset
27
27
28 _md5 = None
28 _md5 = None
29 def md5(s):
29 def md5(s):
30 global _md5
30 global _md5
31 if _md5 is None:
31 if _md5 is None:
32 try:
32 try:
33 import hashlib
33 import hashlib
34 _md5 = hashlib.md5
34 _md5 = hashlib.md5
35 except ImportError:
35 except ImportError:
36 import md5
36 import md5
37 _md5 = md5.md5
37 _md5 = md5.md5
38 return _md5(s)
38 return _md5(s)
39
39
40 _sha1 = None
40 _sha1 = None
41 def sha1(s):
41 def sha1(s):
42 global _sha1
42 global _sha1
43 if _sha1 is None:
43 if _sha1 is None:
44 try:
44 try:
45 import hashlib
45 import hashlib
46 _sha1 = hashlib.sha1
46 _sha1 = hashlib.sha1
47 except ImportError:
47 except ImportError:
48 import sha
48 import sha
49 _sha1 = sha.sha
49 _sha1 = sha.sha
50 return _sha1(s)
50 return _sha1(s)
51
51
52 try:
52 try:
53 import subprocess
53 import subprocess
54 subprocess.Popen # trigger ImportError early
54 subprocess.Popen # trigger ImportError early
55 closefds = os.name == 'posix'
55 closefds = os.name == 'posix'
56 def popen2(cmd, mode='t', bufsize=-1):
56 def popen2(cmd, mode='t', bufsize=-1):
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 close_fds=closefds,
58 close_fds=closefds,
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
60 return p.stdin, p.stdout
60 return p.stdin, p.stdout
61 def popen3(cmd, mode='t', bufsize=-1):
61 def popen3(cmd, mode='t', bufsize=-1):
62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
63 close_fds=closefds,
63 close_fds=closefds,
64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 stderr=subprocess.PIPE)
65 stderr=subprocess.PIPE)
66 return p.stdin, p.stdout, p.stderr
66 return p.stdin, p.stdout, p.stderr
67 def Popen3(cmd, capturestderr=False, bufsize=-1):
67 def Popen3(cmd, capturestderr=False, bufsize=-1):
68 stderr = capturestderr and subprocess.PIPE or None
68 stderr = capturestderr and subprocess.PIPE or None
69 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
69 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
70 close_fds=closefds,
70 close_fds=closefds,
71 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
71 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
72 stderr=stderr)
72 stderr=stderr)
73 p.fromchild = p.stdout
73 p.fromchild = p.stdout
74 p.tochild = p.stdin
74 p.tochild = p.stdin
75 p.childerr = p.stderr
75 p.childerr = p.stderr
76 return p
76 return p
77 except ImportError:
77 except ImportError:
78 subprocess = None
78 subprocess = None
79 from popen2 import Popen3
79 from popen2 import Popen3
80 popen2 = os.popen2
80 popen2 = os.popen2
81 popen3 = os.popen3
81 popen3 = os.popen3
82
82
83
83
84 try:
84 try:
85 _encoding = os.environ.get("HGENCODING")
85 _encoding = os.environ.get("HGENCODING")
86 if sys.platform == 'darwin' and not _encoding:
86 if sys.platform == 'darwin' and not _encoding:
87 # On darwin, getpreferredencoding ignores the locale environment and
87 # On darwin, getpreferredencoding ignores the locale environment and
88 # always returns mac-roman. We override this if the environment is
88 # always returns mac-roman. We override this if the environment is
89 # not C (has been customized by the user).
89 # not C (has been customized by the user).
90 locale.setlocale(locale.LC_CTYPE, '')
90 locale.setlocale(locale.LC_CTYPE, '')
91 _encoding = locale.getlocale()[1]
91 _encoding = locale.getlocale()[1]
92 if not _encoding:
92 if not _encoding:
93 _encoding = locale.getpreferredencoding() or 'ascii'
93 _encoding = locale.getpreferredencoding() or 'ascii'
94 except locale.Error:
94 except locale.Error:
95 _encoding = 'ascii'
95 _encoding = 'ascii'
96 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
96 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
97 _fallbackencoding = 'ISO-8859-1'
97 _fallbackencoding = 'ISO-8859-1'
98
98
99 def tolocal(s):
99 def tolocal(s):
100 """
100 """
101 Convert a string from internal UTF-8 to local encoding
101 Convert a string from internal UTF-8 to local encoding
102
102
103 All internal strings should be UTF-8 but some repos before the
103 All internal strings should be UTF-8 but some repos before the
104 implementation of locale support may contain latin1 or possibly
104 implementation of locale support may contain latin1 or possibly
105 other character sets. We attempt to decode everything strictly
105 other character sets. We attempt to decode everything strictly
106 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
106 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
107 replace unknown characters.
107 replace unknown characters.
108 """
108 """
109 for e in ('UTF-8', _fallbackencoding):
109 for e in ('UTF-8', _fallbackencoding):
110 try:
110 try:
111 u = s.decode(e) # attempt strict decoding
111 u = s.decode(e) # attempt strict decoding
112 return u.encode(_encoding, "replace")
112 return u.encode(_encoding, "replace")
113 except LookupError, k:
113 except LookupError, k:
114 raise Abort(_("%s, please check your locale settings") % k)
114 raise Abort(_("%s, please check your locale settings") % k)
115 except UnicodeDecodeError:
115 except UnicodeDecodeError:
116 pass
116 pass
117 u = s.decode("utf-8", "replace") # last ditch
117 u = s.decode("utf-8", "replace") # last ditch
118 return u.encode(_encoding, "replace")
118 return u.encode(_encoding, "replace")
119
119
120 def fromlocal(s):
120 def fromlocal(s):
121 """
121 """
122 Convert a string from the local character encoding to UTF-8
122 Convert a string from the local character encoding to UTF-8
123
123
124 We attempt to decode strings using the encoding mode set by
124 We attempt to decode strings using the encoding mode set by
125 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
125 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
126 characters will cause an error message. Other modes include
126 characters will cause an error message. Other modes include
127 'replace', which replaces unknown characters with a special
127 'replace', which replaces unknown characters with a special
128 Unicode character, and 'ignore', which drops the character.
128 Unicode character, and 'ignore', which drops the character.
129 """
129 """
130 try:
130 try:
131 return s.decode(_encoding, _encodingmode).encode("utf-8")
131 return s.decode(_encoding, _encodingmode).encode("utf-8")
132 except UnicodeDecodeError, inst:
132 except UnicodeDecodeError, inst:
133 sub = s[max(0, inst.start-10):inst.start+10]
133 sub = s[max(0, inst.start-10):inst.start+10]
134 raise Abort("decoding near '%s': %s!" % (sub, inst))
134 raise Abort("decoding near '%s': %s!" % (sub, inst))
135 except LookupError, k:
135 except LookupError, k:
136 raise Abort(_("%s, please check your locale settings") % k)
136 raise Abort(_("%s, please check your locale settings") % k)
137
137
138 def locallen(s):
138 def locallen(s):
139 """Find the length in characters of a local string"""
139 """Find the length in characters of a local string"""
140 return len(s.decode(_encoding, "replace"))
140 return len(s.decode(_encoding, "replace"))
141
141
142 # used by parsedate
142 # used by parsedate
143 defaultdateformats = (
143 defaultdateformats = (
144 '%Y-%m-%d %H:%M:%S',
144 '%Y-%m-%d %H:%M:%S',
145 '%Y-%m-%d %I:%M:%S%p',
145 '%Y-%m-%d %I:%M:%S%p',
146 '%Y-%m-%d %H:%M',
146 '%Y-%m-%d %H:%M',
147 '%Y-%m-%d %I:%M%p',
147 '%Y-%m-%d %I:%M%p',
148 '%Y-%m-%d',
148 '%Y-%m-%d',
149 '%m-%d',
149 '%m-%d',
150 '%m/%d',
150 '%m/%d',
151 '%m/%d/%y',
151 '%m/%d/%y',
152 '%m/%d/%Y',
152 '%m/%d/%Y',
153 '%a %b %d %H:%M:%S %Y',
153 '%a %b %d %H:%M:%S %Y',
154 '%a %b %d %I:%M:%S%p %Y',
154 '%a %b %d %I:%M:%S%p %Y',
155 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
155 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
156 '%b %d %H:%M:%S %Y',
156 '%b %d %H:%M:%S %Y',
157 '%b %d %I:%M:%S%p %Y',
157 '%b %d %I:%M:%S%p %Y',
158 '%b %d %H:%M:%S',
158 '%b %d %H:%M:%S',
159 '%b %d %I:%M:%S%p',
159 '%b %d %I:%M:%S%p',
160 '%b %d %H:%M',
160 '%b %d %H:%M',
161 '%b %d %I:%M%p',
161 '%b %d %I:%M%p',
162 '%b %d %Y',
162 '%b %d %Y',
163 '%b %d',
163 '%b %d',
164 '%H:%M:%S',
164 '%H:%M:%S',
165 '%I:%M:%SP',
165 '%I:%M:%SP',
166 '%H:%M',
166 '%H:%M',
167 '%I:%M%p',
167 '%I:%M%p',
168 )
168 )
169
169
170 extendeddateformats = defaultdateformats + (
170 extendeddateformats = defaultdateformats + (
171 "%Y",
171 "%Y",
172 "%Y-%m",
172 "%Y-%m",
173 "%b",
173 "%b",
174 "%b %Y",
174 "%b %Y",
175 )
175 )
176
176
177 class SignalInterrupt(Exception):
177 class SignalInterrupt(Exception):
178 """Exception raised on SIGTERM and SIGHUP."""
178 """Exception raised on SIGTERM and SIGHUP."""
179
179
180 # differences from SafeConfigParser:
180 # differences from SafeConfigParser:
181 # - case-sensitive keys
181 # - case-sensitive keys
182 # - allows values that are not strings (this means that you may not
182 # - allows values that are not strings (this means that you may not
183 # be able to save the configuration to a file)
183 # be able to save the configuration to a file)
184 class configparser(ConfigParser.SafeConfigParser):
184 class configparser(ConfigParser.SafeConfigParser):
185 def optionxform(self, optionstr):
185 def optionxform(self, optionstr):
186 return optionstr
186 return optionstr
187
187
188 def set(self, section, option, value):
188 def set(self, section, option, value):
189 return ConfigParser.ConfigParser.set(self, section, option, value)
189 return ConfigParser.ConfigParser.set(self, section, option, value)
190
190
191 def _interpolate(self, section, option, rawval, vars):
191 def _interpolate(self, section, option, rawval, vars):
192 if not isinstance(rawval, basestring):
192 if not isinstance(rawval, basestring):
193 return rawval
193 return rawval
194 return ConfigParser.SafeConfigParser._interpolate(self, section,
194 return ConfigParser.SafeConfigParser._interpolate(self, section,
195 option, rawval, vars)
195 option, rawval, vars)
196
196
197 def cachefunc(func):
197 def cachefunc(func):
198 '''cache the result of function calls'''
198 '''cache the result of function calls'''
199 # XXX doesn't handle keywords args
199 # XXX doesn't handle keywords args
200 cache = {}
200 cache = {}
201 if func.func_code.co_argcount == 1:
201 if func.func_code.co_argcount == 1:
202 # we gain a small amount of time because
202 # we gain a small amount of time because
203 # we don't need to pack/unpack the list
203 # we don't need to pack/unpack the list
204 def f(arg):
204 def f(arg):
205 if arg not in cache:
205 if arg not in cache:
206 cache[arg] = func(arg)
206 cache[arg] = func(arg)
207 return cache[arg]
207 return cache[arg]
208 else:
208 else:
209 def f(*args):
209 def f(*args):
210 if args not in cache:
210 if args not in cache:
211 cache[args] = func(*args)
211 cache[args] = func(*args)
212 return cache[args]
212 return cache[args]
213
213
214 return f
214 return f
215
215
216 def pipefilter(s, cmd):
216 def pipefilter(s, cmd):
217 '''filter string S through command CMD, returning its output'''
217 '''filter string S through command CMD, returning its output'''
218 (pin, pout) = popen2(cmd, 'b')
218 (pin, pout) = popen2(cmd, 'b')
219 def writer():
219 def writer():
220 try:
220 try:
221 pin.write(s)
221 pin.write(s)
222 pin.close()
222 pin.close()
223 except IOError, inst:
223 except IOError, inst:
224 if inst.errno != errno.EPIPE:
224 if inst.errno != errno.EPIPE:
225 raise
225 raise
226
226
227 # we should use select instead on UNIX, but this will work on most
227 # we should use select instead on UNIX, but this will work on most
228 # systems, including Windows
228 # systems, including Windows
229 w = threading.Thread(target=writer)
229 w = threading.Thread(target=writer)
230 w.start()
230 w.start()
231 f = pout.read()
231 f = pout.read()
232 pout.close()
232 pout.close()
233 w.join()
233 w.join()
234 return f
234 return f
235
235
236 def tempfilter(s, cmd):
236 def tempfilter(s, cmd):
237 '''filter string S through a pair of temporary files with CMD.
237 '''filter string S through a pair of temporary files with CMD.
238 CMD is used as a template to create the real command to be run,
238 CMD is used as a template to create the real command to be run,
239 with the strings INFILE and OUTFILE replaced by the real names of
239 with the strings INFILE and OUTFILE replaced by the real names of
240 the temporary files generated.'''
240 the temporary files generated.'''
241 inname, outname = None, None
241 inname, outname = None, None
242 try:
242 try:
243 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
243 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
244 fp = os.fdopen(infd, 'wb')
244 fp = os.fdopen(infd, 'wb')
245 fp.write(s)
245 fp.write(s)
246 fp.close()
246 fp.close()
247 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
247 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
248 os.close(outfd)
248 os.close(outfd)
249 cmd = cmd.replace('INFILE', inname)
249 cmd = cmd.replace('INFILE', inname)
250 cmd = cmd.replace('OUTFILE', outname)
250 cmd = cmd.replace('OUTFILE', outname)
251 code = os.system(cmd)
251 code = os.system(cmd)
252 if sys.platform == 'OpenVMS' and code & 1:
252 if sys.platform == 'OpenVMS' and code & 1:
253 code = 0
253 code = 0
254 if code: raise Abort(_("command '%s' failed: %s") %
254 if code: raise Abort(_("command '%s' failed: %s") %
255 (cmd, explain_exit(code)))
255 (cmd, explain_exit(code)))
256 return open(outname, 'rb').read()
256 return open(outname, 'rb').read()
257 finally:
257 finally:
258 try:
258 try:
259 if inname: os.unlink(inname)
259 if inname: os.unlink(inname)
260 except: pass
260 except: pass
261 try:
261 try:
262 if outname: os.unlink(outname)
262 if outname: os.unlink(outname)
263 except: pass
263 except: pass
264
264
265 filtertable = {
265 filtertable = {
266 'tempfile:': tempfilter,
266 'tempfile:': tempfilter,
267 'pipe:': pipefilter,
267 'pipe:': pipefilter,
268 }
268 }
269
269
270 def filter(s, cmd):
270 def filter(s, cmd):
271 "filter a string through a command that transforms its input to its output"
271 "filter a string through a command that transforms its input to its output"
272 for name, fn in filtertable.iteritems():
272 for name, fn in filtertable.iteritems():
273 if cmd.startswith(name):
273 if cmd.startswith(name):
274 return fn(s, cmd[len(name):].lstrip())
274 return fn(s, cmd[len(name):].lstrip())
275 return pipefilter(s, cmd)
275 return pipefilter(s, cmd)
276
276
277 def binary(s):
277 def binary(s):
278 """return true if a string is binary data"""
278 """return true if a string is binary data"""
279 if s and '\0' in s:
279 if s and '\0' in s:
280 return True
280 return True
281 return False
281 return False
282
282
283 def unique(g):
283 def unique(g):
284 """return the uniq elements of iterable g"""
284 """return the uniq elements of iterable g"""
285 return dict.fromkeys(g).keys()
285 return dict.fromkeys(g).keys()
286
286
287 def sort(l):
287 def sort(l):
288 if not isinstance(l, list):
288 if not isinstance(l, list):
289 l = list(l)
289 l = list(l)
290 l.sort()
290 l.sort()
291 return l
291 return l
292
292
293 def increasingchunks(source, min=1024, max=65536):
294 '''return no less than min bytes per chunk while data remains,
295 doubling min after each chunk until it reaches max'''
296 def log2(x):
297 if not x:
298 return 0
299 i = 0
300 while x:
301 x >>= 1
302 i += 1
303 return i - 1
304
305 buf = []
306 blen = 0
307 for chunk in source:
308 buf.append(chunk)
309 blen += len(chunk)
310 if blen >= min:
311 if min < max:
312 min = min << 1
313 nmin = 1 << log2(blen)
314 if nmin > min:
315 min = nmin
316 if min > max:
317 min = max
318 yield ''.join(buf)
319 blen = 0
320 buf = []
321 if buf:
322 yield ''.join(buf)
323
293 class Abort(Exception):
324 class Abort(Exception):
294 """Raised if a command needs to print an error and exit."""
325 """Raised if a command needs to print an error and exit."""
295
326
296 class UnexpectedOutput(Abort):
327 class UnexpectedOutput(Abort):
297 """Raised to print an error with part of output and exit."""
328 """Raised to print an error with part of output and exit."""
298
329
299 def always(fn): return True
330 def always(fn): return True
300 def never(fn): return False
331 def never(fn): return False
301
332
302 def expand_glob(pats):
333 def expand_glob(pats):
303 '''On Windows, expand the implicit globs in a list of patterns'''
334 '''On Windows, expand the implicit globs in a list of patterns'''
304 if os.name != 'nt':
335 if os.name != 'nt':
305 return list(pats)
336 return list(pats)
306 ret = []
337 ret = []
307 for p in pats:
338 for p in pats:
308 kind, name = patkind(p, None)
339 kind, name = patkind(p, None)
309 if kind is None:
340 if kind is None:
310 globbed = glob.glob(name)
341 globbed = glob.glob(name)
311 if globbed:
342 if globbed:
312 ret.extend(globbed)
343 ret.extend(globbed)
313 continue
344 continue
314 # if we couldn't expand the glob, just keep it around
345 # if we couldn't expand the glob, just keep it around
315 ret.append(p)
346 ret.append(p)
316 return ret
347 return ret
317
348
318 def patkind(name, default):
349 def patkind(name, default):
319 """Split a string into an optional pattern kind prefix and the
350 """Split a string into an optional pattern kind prefix and the
320 actual pattern."""
351 actual pattern."""
321 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
352 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
322 if name.startswith(prefix + ':'): return name.split(':', 1)
353 if name.startswith(prefix + ':'): return name.split(':', 1)
323 return default, name
354 return default, name
324
355
325 def globre(pat, head='^', tail='$'):
356 def globre(pat, head='^', tail='$'):
326 "convert a glob pattern into a regexp"
357 "convert a glob pattern into a regexp"
327 i, n = 0, len(pat)
358 i, n = 0, len(pat)
328 res = ''
359 res = ''
329 group = 0
360 group = 0
330 def peek(): return i < n and pat[i]
361 def peek(): return i < n and pat[i]
331 while i < n:
362 while i < n:
332 c = pat[i]
363 c = pat[i]
333 i = i+1
364 i = i+1
334 if c == '*':
365 if c == '*':
335 if peek() == '*':
366 if peek() == '*':
336 i += 1
367 i += 1
337 res += '.*'
368 res += '.*'
338 else:
369 else:
339 res += '[^/]*'
370 res += '[^/]*'
340 elif c == '?':
371 elif c == '?':
341 res += '.'
372 res += '.'
342 elif c == '[':
373 elif c == '[':
343 j = i
374 j = i
344 if j < n and pat[j] in '!]':
375 if j < n and pat[j] in '!]':
345 j += 1
376 j += 1
346 while j < n and pat[j] != ']':
377 while j < n and pat[j] != ']':
347 j += 1
378 j += 1
348 if j >= n:
379 if j >= n:
349 res += '\\['
380 res += '\\['
350 else:
381 else:
351 stuff = pat[i:j].replace('\\','\\\\')
382 stuff = pat[i:j].replace('\\','\\\\')
352 i = j + 1
383 i = j + 1
353 if stuff[0] == '!':
384 if stuff[0] == '!':
354 stuff = '^' + stuff[1:]
385 stuff = '^' + stuff[1:]
355 elif stuff[0] == '^':
386 elif stuff[0] == '^':
356 stuff = '\\' + stuff
387 stuff = '\\' + stuff
357 res = '%s[%s]' % (res, stuff)
388 res = '%s[%s]' % (res, stuff)
358 elif c == '{':
389 elif c == '{':
359 group += 1
390 group += 1
360 res += '(?:'
391 res += '(?:'
361 elif c == '}' and group:
392 elif c == '}' and group:
362 res += ')'
393 res += ')'
363 group -= 1
394 group -= 1
364 elif c == ',' and group:
395 elif c == ',' and group:
365 res += '|'
396 res += '|'
366 elif c == '\\':
397 elif c == '\\':
367 p = peek()
398 p = peek()
368 if p:
399 if p:
369 i += 1
400 i += 1
370 res += re.escape(p)
401 res += re.escape(p)
371 else:
402 else:
372 res += re.escape(c)
403 res += re.escape(c)
373 else:
404 else:
374 res += re.escape(c)
405 res += re.escape(c)
375 return head + res + tail
406 return head + res + tail
376
407
377 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
408 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
378
409
379 def pathto(root, n1, n2):
410 def pathto(root, n1, n2):
380 '''return the relative path from one place to another.
411 '''return the relative path from one place to another.
381 root should use os.sep to separate directories
412 root should use os.sep to separate directories
382 n1 should use os.sep to separate directories
413 n1 should use os.sep to separate directories
383 n2 should use "/" to separate directories
414 n2 should use "/" to separate directories
384 returns an os.sep-separated path.
415 returns an os.sep-separated path.
385
416
386 If n1 is a relative path, it's assumed it's
417 If n1 is a relative path, it's assumed it's
387 relative to root.
418 relative to root.
388 n2 should always be relative to root.
419 n2 should always be relative to root.
389 '''
420 '''
390 if not n1: return localpath(n2)
421 if not n1: return localpath(n2)
391 if os.path.isabs(n1):
422 if os.path.isabs(n1):
392 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
423 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
393 return os.path.join(root, localpath(n2))
424 return os.path.join(root, localpath(n2))
394 n2 = '/'.join((pconvert(root), n2))
425 n2 = '/'.join((pconvert(root), n2))
395 a, b = splitpath(n1), n2.split('/')
426 a, b = splitpath(n1), n2.split('/')
396 a.reverse()
427 a.reverse()
397 b.reverse()
428 b.reverse()
398 while a and b and a[-1] == b[-1]:
429 while a and b and a[-1] == b[-1]:
399 a.pop()
430 a.pop()
400 b.pop()
431 b.pop()
401 b.reverse()
432 b.reverse()
402 return os.sep.join((['..'] * len(a)) + b) or '.'
433 return os.sep.join((['..'] * len(a)) + b) or '.'
403
434
404 def canonpath(root, cwd, myname):
435 def canonpath(root, cwd, myname):
405 """return the canonical path of myname, given cwd and root"""
436 """return the canonical path of myname, given cwd and root"""
406 if root == os.sep:
437 if root == os.sep:
407 rootsep = os.sep
438 rootsep = os.sep
408 elif endswithsep(root):
439 elif endswithsep(root):
409 rootsep = root
440 rootsep = root
410 else:
441 else:
411 rootsep = root + os.sep
442 rootsep = root + os.sep
412 name = myname
443 name = myname
413 if not os.path.isabs(name):
444 if not os.path.isabs(name):
414 name = os.path.join(root, cwd, name)
445 name = os.path.join(root, cwd, name)
415 name = os.path.normpath(name)
446 name = os.path.normpath(name)
416 audit_path = path_auditor(root)
447 audit_path = path_auditor(root)
417 if name != rootsep and name.startswith(rootsep):
448 if name != rootsep and name.startswith(rootsep):
418 name = name[len(rootsep):]
449 name = name[len(rootsep):]
419 audit_path(name)
450 audit_path(name)
420 return pconvert(name)
451 return pconvert(name)
421 elif name == root:
452 elif name == root:
422 return ''
453 return ''
423 else:
454 else:
424 # Determine whether `name' is in the hierarchy at or beneath `root',
455 # Determine whether `name' is in the hierarchy at or beneath `root',
425 # by iterating name=dirname(name) until that causes no change (can't
456 # by iterating name=dirname(name) until that causes no change (can't
426 # check name == '/', because that doesn't work on windows). For each
457 # check name == '/', because that doesn't work on windows). For each
427 # `name', compare dev/inode numbers. If they match, the list `rel'
458 # `name', compare dev/inode numbers. If they match, the list `rel'
428 # holds the reversed list of components making up the relative file
459 # holds the reversed list of components making up the relative file
429 # name we want.
460 # name we want.
430 root_st = os.stat(root)
461 root_st = os.stat(root)
431 rel = []
462 rel = []
432 while True:
463 while True:
433 try:
464 try:
434 name_st = os.stat(name)
465 name_st = os.stat(name)
435 except OSError:
466 except OSError:
436 break
467 break
437 if samestat(name_st, root_st):
468 if samestat(name_st, root_st):
438 if not rel:
469 if not rel:
439 # name was actually the same as root (maybe a symlink)
470 # name was actually the same as root (maybe a symlink)
440 return ''
471 return ''
441 rel.reverse()
472 rel.reverse()
442 name = os.path.join(*rel)
473 name = os.path.join(*rel)
443 audit_path(name)
474 audit_path(name)
444 return pconvert(name)
475 return pconvert(name)
445 dirname, basename = os.path.split(name)
476 dirname, basename = os.path.split(name)
446 rel.append(basename)
477 rel.append(basename)
447 if dirname == name:
478 if dirname == name:
448 break
479 break
449 name = dirname
480 name = dirname
450
481
451 raise Abort('%s not under root' % myname)
482 raise Abort('%s not under root' % myname)
452
483
453 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
484 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
454 """build a function to match a set of file patterns
485 """build a function to match a set of file patterns
455
486
456 arguments:
487 arguments:
457 canonroot - the canonical root of the tree you're matching against
488 canonroot - the canonical root of the tree you're matching against
458 cwd - the current working directory, if relevant
489 cwd - the current working directory, if relevant
459 names - patterns to find
490 names - patterns to find
460 inc - patterns to include
491 inc - patterns to include
461 exc - patterns to exclude
492 exc - patterns to exclude
462 dflt_pat - if a pattern in names has no explicit type, assume this one
493 dflt_pat - if a pattern in names has no explicit type, assume this one
463 src - where these patterns came from (e.g. .hgignore)
494 src - where these patterns came from (e.g. .hgignore)
464
495
465 a pattern is one of:
496 a pattern is one of:
466 'glob:<glob>' - a glob relative to cwd
497 'glob:<glob>' - a glob relative to cwd
467 're:<regexp>' - a regular expression
498 're:<regexp>' - a regular expression
468 'path:<path>' - a path relative to canonroot
499 'path:<path>' - a path relative to canonroot
469 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
500 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
470 'relpath:<path>' - a path relative to cwd
501 'relpath:<path>' - a path relative to cwd
471 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
502 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
472 '<something>' - one of the cases above, selected by the dflt_pat argument
503 '<something>' - one of the cases above, selected by the dflt_pat argument
473
504
474 returns:
505 returns:
475 a 3-tuple containing
506 a 3-tuple containing
476 - list of roots (places where one should start a recursive walk of the fs);
507 - list of roots (places where one should start a recursive walk of the fs);
477 this often matches the explicit non-pattern names passed in, but also
508 this often matches the explicit non-pattern names passed in, but also
478 includes the initial part of glob: patterns that has no glob characters
509 includes the initial part of glob: patterns that has no glob characters
479 - a bool match(filename) function
510 - a bool match(filename) function
480 - a bool indicating if any patterns were passed in
511 - a bool indicating if any patterns were passed in
481 """
512 """
482
513
483 # a common case: no patterns at all
514 # a common case: no patterns at all
484 if not names and not inc and not exc:
515 if not names and not inc and not exc:
485 return [], always, False
516 return [], always, False
486
517
487 def contains_glob(name):
518 def contains_glob(name):
488 for c in name:
519 for c in name:
489 if c in _globchars: return True
520 if c in _globchars: return True
490 return False
521 return False
491
522
492 def regex(kind, name, tail):
523 def regex(kind, name, tail):
493 '''convert a pattern into a regular expression'''
524 '''convert a pattern into a regular expression'''
494 if not name:
525 if not name:
495 return ''
526 return ''
496 if kind == 're':
527 if kind == 're':
497 return name
528 return name
498 elif kind == 'path':
529 elif kind == 'path':
499 return '^' + re.escape(name) + '(?:/|$)'
530 return '^' + re.escape(name) + '(?:/|$)'
500 elif kind == 'relglob':
531 elif kind == 'relglob':
501 return globre(name, '(?:|.*/)', tail)
532 return globre(name, '(?:|.*/)', tail)
502 elif kind == 'relpath':
533 elif kind == 'relpath':
503 return re.escape(name) + '(?:/|$)'
534 return re.escape(name) + '(?:/|$)'
504 elif kind == 'relre':
535 elif kind == 'relre':
505 if name.startswith('^'):
536 if name.startswith('^'):
506 return name
537 return name
507 return '.*' + name
538 return '.*' + name
508 return globre(name, '', tail)
539 return globre(name, '', tail)
509
540
510 def matchfn(pats, tail):
541 def matchfn(pats, tail):
511 """build a matching function from a set of patterns"""
542 """build a matching function from a set of patterns"""
512 if not pats:
543 if not pats:
513 return
544 return
514 try:
545 try:
515 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
546 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
516 if len(pat) > 20000:
547 if len(pat) > 20000:
517 raise OverflowError()
548 raise OverflowError()
518 return re.compile(pat).match
549 return re.compile(pat).match
519 except OverflowError:
550 except OverflowError:
520 # We're using a Python with a tiny regex engine and we
551 # We're using a Python with a tiny regex engine and we
521 # made it explode, so we'll divide the pattern list in two
552 # made it explode, so we'll divide the pattern list in two
522 # until it works
553 # until it works
523 l = len(pats)
554 l = len(pats)
524 if l < 2:
555 if l < 2:
525 raise
556 raise
526 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
557 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
527 return lambda s: a(s) or b(s)
558 return lambda s: a(s) or b(s)
528 except re.error:
559 except re.error:
529 for k, p in pats:
560 for k, p in pats:
530 try:
561 try:
531 re.compile('(?:%s)' % regex(k, p, tail))
562 re.compile('(?:%s)' % regex(k, p, tail))
532 except re.error:
563 except re.error:
533 if src:
564 if src:
534 raise Abort("%s: invalid pattern (%s): %s" %
565 raise Abort("%s: invalid pattern (%s): %s" %
535 (src, k, p))
566 (src, k, p))
536 else:
567 else:
537 raise Abort("invalid pattern (%s): %s" % (k, p))
568 raise Abort("invalid pattern (%s): %s" % (k, p))
538 raise Abort("invalid pattern")
569 raise Abort("invalid pattern")
539
570
540 def globprefix(pat):
571 def globprefix(pat):
541 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
572 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
542 root = []
573 root = []
543 for p in pat.split('/'):
574 for p in pat.split('/'):
544 if contains_glob(p): break
575 if contains_glob(p): break
545 root.append(p)
576 root.append(p)
546 return '/'.join(root) or '.'
577 return '/'.join(root) or '.'
547
578
548 def normalizepats(names, default):
579 def normalizepats(names, default):
549 pats = []
580 pats = []
550 roots = []
581 roots = []
551 anypats = False
582 anypats = False
552 for kind, name in [patkind(p, default) for p in names]:
583 for kind, name in [patkind(p, default) for p in names]:
553 if kind in ('glob', 'relpath'):
584 if kind in ('glob', 'relpath'):
554 name = canonpath(canonroot, cwd, name)
585 name = canonpath(canonroot, cwd, name)
555 elif kind in ('relglob', 'path'):
586 elif kind in ('relglob', 'path'):
556 name = normpath(name)
587 name = normpath(name)
557
588
558 pats.append((kind, name))
589 pats.append((kind, name))
559
590
560 if kind in ('glob', 're', 'relglob', 'relre'):
591 if kind in ('glob', 're', 'relglob', 'relre'):
561 anypats = True
592 anypats = True
562
593
563 if kind == 'glob':
594 if kind == 'glob':
564 root = globprefix(name)
595 root = globprefix(name)
565 roots.append(root)
596 roots.append(root)
566 elif kind in ('relpath', 'path'):
597 elif kind in ('relpath', 'path'):
567 roots.append(name or '.')
598 roots.append(name or '.')
568 elif kind == 'relglob':
599 elif kind == 'relglob':
569 roots.append('.')
600 roots.append('.')
570 return roots, pats, anypats
601 return roots, pats, anypats
571
602
572 roots, pats, anypats = normalizepats(names, dflt_pat)
603 roots, pats, anypats = normalizepats(names, dflt_pat)
573
604
574 patmatch = matchfn(pats, '$') or always
605 patmatch = matchfn(pats, '$') or always
575 incmatch = always
606 incmatch = always
576 if inc:
607 if inc:
577 dummy, inckinds, dummy = normalizepats(inc, 'glob')
608 dummy, inckinds, dummy = normalizepats(inc, 'glob')
578 incmatch = matchfn(inckinds, '(?:/|$)')
609 incmatch = matchfn(inckinds, '(?:/|$)')
579 excmatch = lambda fn: False
610 excmatch = lambda fn: False
580 if exc:
611 if exc:
581 dummy, exckinds, dummy = normalizepats(exc, 'glob')
612 dummy, exckinds, dummy = normalizepats(exc, 'glob')
582 excmatch = matchfn(exckinds, '(?:/|$)')
613 excmatch = matchfn(exckinds, '(?:/|$)')
583
614
584 if not names and inc and not exc:
615 if not names and inc and not exc:
585 # common case: hgignore patterns
616 # common case: hgignore patterns
586 match = incmatch
617 match = incmatch
587 else:
618 else:
588 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
619 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
589
620
590 return (roots, match, (inc or exc or anypats) and True)
621 return (roots, match, (inc or exc or anypats) and True)
591
622
592 _hgexecutable = None
623 _hgexecutable = None
593
624
594 def main_is_frozen():
625 def main_is_frozen():
595 """return True if we are a frozen executable.
626 """return True if we are a frozen executable.
596
627
597 The code supports py2exe (most common, Windows only) and tools/freeze
628 The code supports py2exe (most common, Windows only) and tools/freeze
598 (portable, not much used).
629 (portable, not much used).
599 """
630 """
600 return (hasattr(sys, "frozen") or # new py2exe
631 return (hasattr(sys, "frozen") or # new py2exe
601 hasattr(sys, "importers") or # old py2exe
632 hasattr(sys, "importers") or # old py2exe
602 imp.is_frozen("__main__")) # tools/freeze
633 imp.is_frozen("__main__")) # tools/freeze
603
634
604 def hgexecutable():
635 def hgexecutable():
605 """return location of the 'hg' executable.
636 """return location of the 'hg' executable.
606
637
607 Defaults to $HG or 'hg' in the search path.
638 Defaults to $HG or 'hg' in the search path.
608 """
639 """
609 if _hgexecutable is None:
640 if _hgexecutable is None:
610 hg = os.environ.get('HG')
641 hg = os.environ.get('HG')
611 if hg:
642 if hg:
612 set_hgexecutable(hg)
643 set_hgexecutable(hg)
613 elif main_is_frozen():
644 elif main_is_frozen():
614 set_hgexecutable(sys.executable)
645 set_hgexecutable(sys.executable)
615 else:
646 else:
616 set_hgexecutable(find_exe('hg', 'hg'))
647 set_hgexecutable(find_exe('hg', 'hg'))
617 return _hgexecutable
648 return _hgexecutable
618
649
619 def set_hgexecutable(path):
650 def set_hgexecutable(path):
620 """set location of the 'hg' executable"""
651 """set location of the 'hg' executable"""
621 global _hgexecutable
652 global _hgexecutable
622 _hgexecutable = path
653 _hgexecutable = path
623
654
624 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
655 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
625 '''enhanced shell command execution.
656 '''enhanced shell command execution.
626 run with environment maybe modified, maybe in different dir.
657 run with environment maybe modified, maybe in different dir.
627
658
628 if command fails and onerr is None, return status. if ui object,
659 if command fails and onerr is None, return status. if ui object,
629 print error message and return status, else raise onerr object as
660 print error message and return status, else raise onerr object as
630 exception.'''
661 exception.'''
631 def py2shell(val):
662 def py2shell(val):
632 'convert python object into string that is useful to shell'
663 'convert python object into string that is useful to shell'
633 if val in (None, False):
664 if val in (None, False):
634 return '0'
665 return '0'
635 if val == True:
666 if val == True:
636 return '1'
667 return '1'
637 return str(val)
668 return str(val)
638 oldenv = {}
669 oldenv = {}
639 for k in environ:
670 for k in environ:
640 oldenv[k] = os.environ.get(k)
671 oldenv[k] = os.environ.get(k)
641 if cwd is not None:
672 if cwd is not None:
642 oldcwd = os.getcwd()
673 oldcwd = os.getcwd()
643 origcmd = cmd
674 origcmd = cmd
644 if os.name == 'nt':
675 if os.name == 'nt':
645 cmd = '"%s"' % cmd
676 cmd = '"%s"' % cmd
646 try:
677 try:
647 for k, v in environ.iteritems():
678 for k, v in environ.iteritems():
648 os.environ[k] = py2shell(v)
679 os.environ[k] = py2shell(v)
649 os.environ['HG'] = hgexecutable()
680 os.environ['HG'] = hgexecutable()
650 if cwd is not None and oldcwd != cwd:
681 if cwd is not None and oldcwd != cwd:
651 os.chdir(cwd)
682 os.chdir(cwd)
652 rc = os.system(cmd)
683 rc = os.system(cmd)
653 if sys.platform == 'OpenVMS' and rc & 1:
684 if sys.platform == 'OpenVMS' and rc & 1:
654 rc = 0
685 rc = 0
655 if rc and onerr:
686 if rc and onerr:
656 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
687 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
657 explain_exit(rc)[0])
688 explain_exit(rc)[0])
658 if errprefix:
689 if errprefix:
659 errmsg = '%s: %s' % (errprefix, errmsg)
690 errmsg = '%s: %s' % (errprefix, errmsg)
660 try:
691 try:
661 onerr.warn(errmsg + '\n')
692 onerr.warn(errmsg + '\n')
662 except AttributeError:
693 except AttributeError:
663 raise onerr(errmsg)
694 raise onerr(errmsg)
664 return rc
695 return rc
665 finally:
696 finally:
666 for k, v in oldenv.iteritems():
697 for k, v in oldenv.iteritems():
667 if v is None:
698 if v is None:
668 del os.environ[k]
699 del os.environ[k]
669 else:
700 else:
670 os.environ[k] = v
701 os.environ[k] = v
671 if cwd is not None and oldcwd != cwd:
702 if cwd is not None and oldcwd != cwd:
672 os.chdir(oldcwd)
703 os.chdir(oldcwd)
673
704
674 # os.path.lexists is not available on python2.3
705 # os.path.lexists is not available on python2.3
675 def lexists(filename):
706 def lexists(filename):
676 "test whether a file with this name exists. does not follow symlinks"
707 "test whether a file with this name exists. does not follow symlinks"
677 try:
708 try:
678 os.lstat(filename)
709 os.lstat(filename)
679 except:
710 except:
680 return False
711 return False
681 return True
712 return True
682
713
683 def rename(src, dst):
714 def rename(src, dst):
684 """forcibly rename a file"""
715 """forcibly rename a file"""
685 try:
716 try:
686 os.rename(src, dst)
717 os.rename(src, dst)
687 except OSError, err: # FIXME: check err (EEXIST ?)
718 except OSError, err: # FIXME: check err (EEXIST ?)
688 # on windows, rename to existing file is not allowed, so we
719 # on windows, rename to existing file is not allowed, so we
689 # must delete destination first. but if file is open, unlink
720 # must delete destination first. but if file is open, unlink
690 # schedules it for delete but does not delete it. rename
721 # schedules it for delete but does not delete it. rename
691 # happens immediately even for open files, so we create
722 # happens immediately even for open files, so we create
692 # temporary file, delete it, rename destination to that name,
723 # temporary file, delete it, rename destination to that name,
693 # then delete that. then rename is safe to do.
724 # then delete that. then rename is safe to do.
694 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
725 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
695 os.close(fd)
726 os.close(fd)
696 os.unlink(temp)
727 os.unlink(temp)
697 os.rename(dst, temp)
728 os.rename(dst, temp)
698 os.unlink(temp)
729 os.unlink(temp)
699 os.rename(src, dst)
730 os.rename(src, dst)
700
731
701 def unlink(f):
732 def unlink(f):
702 """unlink and remove the directory if it is empty"""
733 """unlink and remove the directory if it is empty"""
703 os.unlink(f)
734 os.unlink(f)
704 # try removing directories that might now be empty
735 # try removing directories that might now be empty
705 try:
736 try:
706 os.removedirs(os.path.dirname(f))
737 os.removedirs(os.path.dirname(f))
707 except OSError:
738 except OSError:
708 pass
739 pass
709
740
710 def copyfile(src, dest):
741 def copyfile(src, dest):
711 "copy a file, preserving mode"
742 "copy a file, preserving mode"
712 if os.path.islink(src):
743 if os.path.islink(src):
713 try:
744 try:
714 os.unlink(dest)
745 os.unlink(dest)
715 except:
746 except:
716 pass
747 pass
717 os.symlink(os.readlink(src), dest)
748 os.symlink(os.readlink(src), dest)
718 else:
749 else:
719 try:
750 try:
720 shutil.copyfile(src, dest)
751 shutil.copyfile(src, dest)
721 shutil.copymode(src, dest)
752 shutil.copymode(src, dest)
722 except shutil.Error, inst:
753 except shutil.Error, inst:
723 raise Abort(str(inst))
754 raise Abort(str(inst))
724
755
725 def copyfiles(src, dst, hardlink=None):
756 def copyfiles(src, dst, hardlink=None):
726 """Copy a directory tree using hardlinks if possible"""
757 """Copy a directory tree using hardlinks if possible"""
727
758
728 if hardlink is None:
759 if hardlink is None:
729 hardlink = (os.stat(src).st_dev ==
760 hardlink = (os.stat(src).st_dev ==
730 os.stat(os.path.dirname(dst)).st_dev)
761 os.stat(os.path.dirname(dst)).st_dev)
731
762
732 if os.path.isdir(src):
763 if os.path.isdir(src):
733 os.mkdir(dst)
764 os.mkdir(dst)
734 for name, kind in osutil.listdir(src):
765 for name, kind in osutil.listdir(src):
735 srcname = os.path.join(src, name)
766 srcname = os.path.join(src, name)
736 dstname = os.path.join(dst, name)
767 dstname = os.path.join(dst, name)
737 copyfiles(srcname, dstname, hardlink)
768 copyfiles(srcname, dstname, hardlink)
738 else:
769 else:
739 if hardlink:
770 if hardlink:
740 try:
771 try:
741 os_link(src, dst)
772 os_link(src, dst)
742 except (IOError, OSError):
773 except (IOError, OSError):
743 hardlink = False
774 hardlink = False
744 shutil.copy(src, dst)
775 shutil.copy(src, dst)
745 else:
776 else:
746 shutil.copy(src, dst)
777 shutil.copy(src, dst)
747
778
748 class path_auditor(object):
779 class path_auditor(object):
749 '''ensure that a filesystem path contains no banned components.
780 '''ensure that a filesystem path contains no banned components.
750 the following properties of a path are checked:
781 the following properties of a path are checked:
751
782
752 - under top-level .hg
783 - under top-level .hg
753 - starts at the root of a windows drive
784 - starts at the root of a windows drive
754 - contains ".."
785 - contains ".."
755 - traverses a symlink (e.g. a/symlink_here/b)
786 - traverses a symlink (e.g. a/symlink_here/b)
756 - inside a nested repository'''
787 - inside a nested repository'''
757
788
758 def __init__(self, root):
789 def __init__(self, root):
759 self.audited = set()
790 self.audited = set()
760 self.auditeddir = set()
791 self.auditeddir = set()
761 self.root = root
792 self.root = root
762
793
763 def __call__(self, path):
794 def __call__(self, path):
764 if path in self.audited:
795 if path in self.audited:
765 return
796 return
766 normpath = os.path.normcase(path)
797 normpath = os.path.normcase(path)
767 parts = splitpath(normpath)
798 parts = splitpath(normpath)
768 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
799 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
769 or os.pardir in parts):
800 or os.pardir in parts):
770 raise Abort(_("path contains illegal component: %s") % path)
801 raise Abort(_("path contains illegal component: %s") % path)
771 def check(prefix):
802 def check(prefix):
772 curpath = os.path.join(self.root, prefix)
803 curpath = os.path.join(self.root, prefix)
773 try:
804 try:
774 st = os.lstat(curpath)
805 st = os.lstat(curpath)
775 except OSError, err:
806 except OSError, err:
776 # EINVAL can be raised as invalid path syntax under win32.
807 # EINVAL can be raised as invalid path syntax under win32.
777 # They must be ignored for patterns can be checked too.
808 # They must be ignored for patterns can be checked too.
778 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
809 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
779 raise
810 raise
780 else:
811 else:
781 if stat.S_ISLNK(st.st_mode):
812 if stat.S_ISLNK(st.st_mode):
782 raise Abort(_('path %r traverses symbolic link %r') %
813 raise Abort(_('path %r traverses symbolic link %r') %
783 (path, prefix))
814 (path, prefix))
784 elif (stat.S_ISDIR(st.st_mode) and
815 elif (stat.S_ISDIR(st.st_mode) and
785 os.path.isdir(os.path.join(curpath, '.hg'))):
816 os.path.isdir(os.path.join(curpath, '.hg'))):
786 raise Abort(_('path %r is inside repo %r') %
817 raise Abort(_('path %r is inside repo %r') %
787 (path, prefix))
818 (path, prefix))
788 parts.pop()
819 parts.pop()
789 prefixes = []
820 prefixes = []
790 for n in range(len(parts)):
821 for n in range(len(parts)):
791 prefix = os.sep.join(parts)
822 prefix = os.sep.join(parts)
792 if prefix in self.auditeddir:
823 if prefix in self.auditeddir:
793 break
824 break
794 check(prefix)
825 check(prefix)
795 prefixes.append(prefix)
826 prefixes.append(prefix)
796 parts.pop()
827 parts.pop()
797
828
798 self.audited.add(path)
829 self.audited.add(path)
799 # only add prefixes to the cache after checking everything: we don't
830 # only add prefixes to the cache after checking everything: we don't
800 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
831 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
801 self.auditeddir.update(prefixes)
832 self.auditeddir.update(prefixes)
802
833
803 def _makelock_file(info, pathname):
834 def _makelock_file(info, pathname):
804 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
835 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
805 os.write(ld, info)
836 os.write(ld, info)
806 os.close(ld)
837 os.close(ld)
807
838
808 def _readlock_file(pathname):
839 def _readlock_file(pathname):
809 return posixfile(pathname).read()
840 return posixfile(pathname).read()
810
841
811 def nlinks(pathname):
842 def nlinks(pathname):
812 """Return number of hardlinks for the given file."""
843 """Return number of hardlinks for the given file."""
813 return os.lstat(pathname).st_nlink
844 return os.lstat(pathname).st_nlink
814
845
815 if hasattr(os, 'link'):
846 if hasattr(os, 'link'):
816 os_link = os.link
847 os_link = os.link
817 else:
848 else:
818 def os_link(src, dst):
849 def os_link(src, dst):
819 raise OSError(0, _("Hardlinks not supported"))
850 raise OSError(0, _("Hardlinks not supported"))
820
851
821 def fstat(fp):
852 def fstat(fp):
822 '''stat file object that may not have fileno method.'''
853 '''stat file object that may not have fileno method.'''
823 try:
854 try:
824 return os.fstat(fp.fileno())
855 return os.fstat(fp.fileno())
825 except AttributeError:
856 except AttributeError:
826 return os.stat(fp.name)
857 return os.stat(fp.name)
827
858
828 posixfile = file
859 posixfile = file
829
860
830 def openhardlinks():
861 def openhardlinks():
831 '''return true if it is safe to hold open file handles to hardlinks'''
862 '''return true if it is safe to hold open file handles to hardlinks'''
832 return True
863 return True
833
864
834 def _statfiles(files):
865 def _statfiles(files):
835 'Stat each file in files and yield stat or None if file does not exist.'
866 'Stat each file in files and yield stat or None if file does not exist.'
836 lstat = os.lstat
867 lstat = os.lstat
837 for nf in files:
868 for nf in files:
838 try:
869 try:
839 st = lstat(nf)
870 st = lstat(nf)
840 except OSError, err:
871 except OSError, err:
841 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
872 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
842 raise
873 raise
843 st = None
874 st = None
844 yield st
875 yield st
845
876
846 def _statfiles_clustered(files):
877 def _statfiles_clustered(files):
847 '''Stat each file in files and yield stat or None if file does not exist.
878 '''Stat each file in files and yield stat or None if file does not exist.
848 Cluster and cache stat per directory to minimize number of OS stat calls.'''
879 Cluster and cache stat per directory to minimize number of OS stat calls.'''
849 lstat = os.lstat
880 lstat = os.lstat
850 ncase = os.path.normcase
881 ncase = os.path.normcase
851 sep = os.sep
882 sep = os.sep
852 dircache = {} # dirname -> filename -> status | None if file does not exist
883 dircache = {} # dirname -> filename -> status | None if file does not exist
853 for nf in files:
884 for nf in files:
854 nf = ncase(nf)
885 nf = ncase(nf)
855 pos = nf.rfind(sep)
886 pos = nf.rfind(sep)
856 if pos == -1:
887 if pos == -1:
857 dir, base = '.', nf
888 dir, base = '.', nf
858 else:
889 else:
859 dir, base = nf[:pos+1], nf[pos+1:]
890 dir, base = nf[:pos+1], nf[pos+1:]
860 cache = dircache.get(dir, None)
891 cache = dircache.get(dir, None)
861 if cache is None:
892 if cache is None:
862 try:
893 try:
863 dmap = dict([(ncase(n), s)
894 dmap = dict([(ncase(n), s)
864 for n, k, s in osutil.listdir(dir, True)])
895 for n, k, s in osutil.listdir(dir, True)])
865 except OSError, err:
896 except OSError, err:
866 # handle directory not found in Python version prior to 2.5
897 # handle directory not found in Python version prior to 2.5
867 # Python <= 2.4 returns native Windows code 3 in errno
898 # Python <= 2.4 returns native Windows code 3 in errno
868 # Python >= 2.5 returns ENOENT and adds winerror field
899 # Python >= 2.5 returns ENOENT and adds winerror field
869 # EINVAL is raised if dir is not a directory.
900 # EINVAL is raised if dir is not a directory.
870 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
901 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
871 errno.ENOTDIR):
902 errno.ENOTDIR):
872 raise
903 raise
873 dmap = {}
904 dmap = {}
874 cache = dircache.setdefault(dir, dmap)
905 cache = dircache.setdefault(dir, dmap)
875 yield cache.get(base, None)
906 yield cache.get(base, None)
876
907
877 if sys.platform == 'win32':
908 if sys.platform == 'win32':
878 statfiles = _statfiles_clustered
909 statfiles = _statfiles_clustered
879 else:
910 else:
880 statfiles = _statfiles
911 statfiles = _statfiles
881
912
882 getuser_fallback = None
913 getuser_fallback = None
883
914
884 def getuser():
915 def getuser():
885 '''return name of current user'''
916 '''return name of current user'''
886 try:
917 try:
887 return getpass.getuser()
918 return getpass.getuser()
888 except ImportError:
919 except ImportError:
889 # import of pwd will fail on windows - try fallback
920 # import of pwd will fail on windows - try fallback
890 if getuser_fallback:
921 if getuser_fallback:
891 return getuser_fallback()
922 return getuser_fallback()
892 # raised if win32api not available
923 # raised if win32api not available
893 raise Abort(_('user name not available - set USERNAME '
924 raise Abort(_('user name not available - set USERNAME '
894 'environment variable'))
925 'environment variable'))
895
926
896 def username(uid=None):
927 def username(uid=None):
897 """Return the name of the user with the given uid.
928 """Return the name of the user with the given uid.
898
929
899 If uid is None, return the name of the current user."""
930 If uid is None, return the name of the current user."""
900 try:
931 try:
901 import pwd
932 import pwd
902 if uid is None:
933 if uid is None:
903 uid = os.getuid()
934 uid = os.getuid()
904 try:
935 try:
905 return pwd.getpwuid(uid)[0]
936 return pwd.getpwuid(uid)[0]
906 except KeyError:
937 except KeyError:
907 return str(uid)
938 return str(uid)
908 except ImportError:
939 except ImportError:
909 return None
940 return None
910
941
911 def groupname(gid=None):
942 def groupname(gid=None):
912 """Return the name of the group with the given gid.
943 """Return the name of the group with the given gid.
913
944
914 If gid is None, return the name of the current group."""
945 If gid is None, return the name of the current group."""
915 try:
946 try:
916 import grp
947 import grp
917 if gid is None:
948 if gid is None:
918 gid = os.getgid()
949 gid = os.getgid()
919 try:
950 try:
920 return grp.getgrgid(gid)[0]
951 return grp.getgrgid(gid)[0]
921 except KeyError:
952 except KeyError:
922 return str(gid)
953 return str(gid)
923 except ImportError:
954 except ImportError:
924 return None
955 return None
925
956
926 # File system features
957 # File system features
927
958
928 def checkcase(path):
959 def checkcase(path):
929 """
960 """
930 Check whether the given path is on a case-sensitive filesystem
961 Check whether the given path is on a case-sensitive filesystem
931
962
932 Requires a path (like /foo/.hg) ending with a foldable final
963 Requires a path (like /foo/.hg) ending with a foldable final
933 directory component.
964 directory component.
934 """
965 """
935 s1 = os.stat(path)
966 s1 = os.stat(path)
936 d, b = os.path.split(path)
967 d, b = os.path.split(path)
937 p2 = os.path.join(d, b.upper())
968 p2 = os.path.join(d, b.upper())
938 if path == p2:
969 if path == p2:
939 p2 = os.path.join(d, b.lower())
970 p2 = os.path.join(d, b.lower())
940 try:
971 try:
941 s2 = os.stat(p2)
972 s2 = os.stat(p2)
942 if s2 == s1:
973 if s2 == s1:
943 return False
974 return False
944 return True
975 return True
945 except:
976 except:
946 return True
977 return True
947
978
948 _fspathcache = {}
979 _fspathcache = {}
949 def fspath(name, root):
980 def fspath(name, root):
950 '''Get name in the case stored in the filesystem
981 '''Get name in the case stored in the filesystem
951
982
952 The name is either relative to root, or it is an absolute path starting
983 The name is either relative to root, or it is an absolute path starting
953 with root. Note that this function is unnecessary, and should not be
984 with root. Note that this function is unnecessary, and should not be
954 called, for case-sensitive filesystems (simply because it's expensive).
985 called, for case-sensitive filesystems (simply because it's expensive).
955 '''
986 '''
956 # If name is absolute, make it relative
987 # If name is absolute, make it relative
957 if name.lower().startswith(root.lower()):
988 if name.lower().startswith(root.lower()):
958 l = len(root)
989 l = len(root)
959 if name[l] == os.sep or name[l] == os.altsep:
990 if name[l] == os.sep or name[l] == os.altsep:
960 l = l + 1
991 l = l + 1
961 name = name[l:]
992 name = name[l:]
962
993
963 if not os.path.exists(os.path.join(root, name)):
994 if not os.path.exists(os.path.join(root, name)):
964 return None
995 return None
965
996
966 seps = os.sep
997 seps = os.sep
967 if os.altsep:
998 if os.altsep:
968 seps = seps + os.altsep
999 seps = seps + os.altsep
969 # Protect backslashes. This gets silly very quickly.
1000 # Protect backslashes. This gets silly very quickly.
970 seps.replace('\\','\\\\')
1001 seps.replace('\\','\\\\')
971 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1002 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
972 dir = os.path.normcase(os.path.normpath(root))
1003 dir = os.path.normcase(os.path.normpath(root))
973 result = []
1004 result = []
974 for part, sep in pattern.findall(name):
1005 for part, sep in pattern.findall(name):
975 if sep:
1006 if sep:
976 result.append(sep)
1007 result.append(sep)
977 continue
1008 continue
978
1009
979 if dir not in _fspathcache:
1010 if dir not in _fspathcache:
980 _fspathcache[dir] = os.listdir(dir)
1011 _fspathcache[dir] = os.listdir(dir)
981 contents = _fspathcache[dir]
1012 contents = _fspathcache[dir]
982
1013
983 lpart = part.lower()
1014 lpart = part.lower()
984 for n in contents:
1015 for n in contents:
985 if n.lower() == lpart:
1016 if n.lower() == lpart:
986 result.append(n)
1017 result.append(n)
987 break
1018 break
988 else:
1019 else:
989 # Cannot happen, as the file exists!
1020 # Cannot happen, as the file exists!
990 result.append(part)
1021 result.append(part)
991 dir = os.path.join(dir, lpart)
1022 dir = os.path.join(dir, lpart)
992
1023
993 return ''.join(result)
1024 return ''.join(result)
994
1025
995 def checkexec(path):
1026 def checkexec(path):
996 """
1027 """
997 Check whether the given path is on a filesystem with UNIX-like exec flags
1028 Check whether the given path is on a filesystem with UNIX-like exec flags
998
1029
999 Requires a directory (like /foo/.hg)
1030 Requires a directory (like /foo/.hg)
1000 """
1031 """
1001
1032
1002 # VFAT on some Linux versions can flip mode but it doesn't persist
1033 # VFAT on some Linux versions can flip mode but it doesn't persist
1003 # a FS remount. Frequently we can detect it if files are created
1034 # a FS remount. Frequently we can detect it if files are created
1004 # with exec bit on.
1035 # with exec bit on.
1005
1036
1006 try:
1037 try:
1007 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1038 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1008 fh, fn = tempfile.mkstemp("", "", path)
1039 fh, fn = tempfile.mkstemp("", "", path)
1009 try:
1040 try:
1010 os.close(fh)
1041 os.close(fh)
1011 m = os.stat(fn).st_mode & 0777
1042 m = os.stat(fn).st_mode & 0777
1012 new_file_has_exec = m & EXECFLAGS
1043 new_file_has_exec = m & EXECFLAGS
1013 os.chmod(fn, m ^ EXECFLAGS)
1044 os.chmod(fn, m ^ EXECFLAGS)
1014 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1045 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1015 finally:
1046 finally:
1016 os.unlink(fn)
1047 os.unlink(fn)
1017 except (IOError, OSError):
1048 except (IOError, OSError):
1018 # we don't care, the user probably won't be able to commit anyway
1049 # we don't care, the user probably won't be able to commit anyway
1019 return False
1050 return False
1020 return not (new_file_has_exec or exec_flags_cannot_flip)
1051 return not (new_file_has_exec or exec_flags_cannot_flip)
1021
1052
1022 def checklink(path):
1053 def checklink(path):
1023 """check whether the given path is on a symlink-capable filesystem"""
1054 """check whether the given path is on a symlink-capable filesystem"""
1024 # mktemp is not racy because symlink creation will fail if the
1055 # mktemp is not racy because symlink creation will fail if the
1025 # file already exists
1056 # file already exists
1026 name = tempfile.mktemp(dir=path)
1057 name = tempfile.mktemp(dir=path)
1027 try:
1058 try:
1028 os.symlink(".", name)
1059 os.symlink(".", name)
1029 os.unlink(name)
1060 os.unlink(name)
1030 return True
1061 return True
1031 except (OSError, AttributeError):
1062 except (OSError, AttributeError):
1032 return False
1063 return False
1033
1064
1034 _umask = os.umask(0)
1065 _umask = os.umask(0)
1035 os.umask(_umask)
1066 os.umask(_umask)
1036
1067
1037 def needbinarypatch():
1068 def needbinarypatch():
1038 """return True if patches should be applied in binary mode by default."""
1069 """return True if patches should be applied in binary mode by default."""
1039 return os.name == 'nt'
1070 return os.name == 'nt'
1040
1071
1041 def endswithsep(path):
1072 def endswithsep(path):
1042 '''Check path ends with os.sep or os.altsep.'''
1073 '''Check path ends with os.sep or os.altsep.'''
1043 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1074 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1044
1075
1045 def splitpath(path):
1076 def splitpath(path):
1046 '''Split path by os.sep.
1077 '''Split path by os.sep.
1047 Note that this function does not use os.altsep because this is
1078 Note that this function does not use os.altsep because this is
1048 an alternative of simple "xxx.split(os.sep)".
1079 an alternative of simple "xxx.split(os.sep)".
1049 It is recommended to use os.path.normpath() before using this
1080 It is recommended to use os.path.normpath() before using this
1050 function if need.'''
1081 function if need.'''
1051 return path.split(os.sep)
1082 return path.split(os.sep)
1052
1083
1053 def gui():
1084 def gui():
1054 '''Are we running in a GUI?'''
1085 '''Are we running in a GUI?'''
1055 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1086 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1056
1087
1057 def lookup_reg(key, name=None, scope=None):
1088 def lookup_reg(key, name=None, scope=None):
1058 return None
1089 return None
1059
1090
1060 # Platform specific variants
1091 # Platform specific variants
1061 if os.name == 'nt':
1092 if os.name == 'nt':
1062 import msvcrt
1093 import msvcrt
1063 nulldev = 'NUL:'
1094 nulldev = 'NUL:'
1064
1095
1065 class winstdout:
1096 class winstdout:
1066 '''stdout on windows misbehaves if sent through a pipe'''
1097 '''stdout on windows misbehaves if sent through a pipe'''
1067
1098
1068 def __init__(self, fp):
1099 def __init__(self, fp):
1069 self.fp = fp
1100 self.fp = fp
1070
1101
1071 def __getattr__(self, key):
1102 def __getattr__(self, key):
1072 return getattr(self.fp, key)
1103 return getattr(self.fp, key)
1073
1104
1074 def close(self):
1105 def close(self):
1075 try:
1106 try:
1076 self.fp.close()
1107 self.fp.close()
1077 except: pass
1108 except: pass
1078
1109
1079 def write(self, s):
1110 def write(self, s):
1080 try:
1111 try:
1081 # This is workaround for "Not enough space" error on
1112 # This is workaround for "Not enough space" error on
1082 # writing large size of data to console.
1113 # writing large size of data to console.
1083 limit = 16000
1114 limit = 16000
1084 l = len(s)
1115 l = len(s)
1085 start = 0
1116 start = 0
1086 while start < l:
1117 while start < l:
1087 end = start + limit
1118 end = start + limit
1088 self.fp.write(s[start:end])
1119 self.fp.write(s[start:end])
1089 start = end
1120 start = end
1090 except IOError, inst:
1121 except IOError, inst:
1091 if inst.errno != 0: raise
1122 if inst.errno != 0: raise
1092 self.close()
1123 self.close()
1093 raise IOError(errno.EPIPE, 'Broken pipe')
1124 raise IOError(errno.EPIPE, 'Broken pipe')
1094
1125
1095 def flush(self):
1126 def flush(self):
1096 try:
1127 try:
1097 return self.fp.flush()
1128 return self.fp.flush()
1098 except IOError, inst:
1129 except IOError, inst:
1099 if inst.errno != errno.EINVAL: raise
1130 if inst.errno != errno.EINVAL: raise
1100 self.close()
1131 self.close()
1101 raise IOError(errno.EPIPE, 'Broken pipe')
1132 raise IOError(errno.EPIPE, 'Broken pipe')
1102
1133
1103 sys.stdout = winstdout(sys.stdout)
1134 sys.stdout = winstdout(sys.stdout)
1104
1135
1105 def _is_win_9x():
1136 def _is_win_9x():
1106 '''return true if run on windows 95, 98 or me.'''
1137 '''return true if run on windows 95, 98 or me.'''
1107 try:
1138 try:
1108 return sys.getwindowsversion()[3] == 1
1139 return sys.getwindowsversion()[3] == 1
1109 except AttributeError:
1140 except AttributeError:
1110 return 'command' in os.environ.get('comspec', '')
1141 return 'command' in os.environ.get('comspec', '')
1111
1142
1112 def openhardlinks():
1143 def openhardlinks():
1113 return not _is_win_9x and "win32api" in locals()
1144 return not _is_win_9x and "win32api" in locals()
1114
1145
1115 def system_rcpath():
1146 def system_rcpath():
1116 try:
1147 try:
1117 return system_rcpath_win32()
1148 return system_rcpath_win32()
1118 except:
1149 except:
1119 return [r'c:\mercurial\mercurial.ini']
1150 return [r'c:\mercurial\mercurial.ini']
1120
1151
1121 def user_rcpath():
1152 def user_rcpath():
1122 '''return os-specific hgrc search path to the user dir'''
1153 '''return os-specific hgrc search path to the user dir'''
1123 try:
1154 try:
1124 path = user_rcpath_win32()
1155 path = user_rcpath_win32()
1125 except:
1156 except:
1126 home = os.path.expanduser('~')
1157 home = os.path.expanduser('~')
1127 path = [os.path.join(home, 'mercurial.ini'),
1158 path = [os.path.join(home, 'mercurial.ini'),
1128 os.path.join(home, '.hgrc')]
1159 os.path.join(home, '.hgrc')]
1129 userprofile = os.environ.get('USERPROFILE')
1160 userprofile = os.environ.get('USERPROFILE')
1130 if userprofile:
1161 if userprofile:
1131 path.append(os.path.join(userprofile, 'mercurial.ini'))
1162 path.append(os.path.join(userprofile, 'mercurial.ini'))
1132 path.append(os.path.join(userprofile, '.hgrc'))
1163 path.append(os.path.join(userprofile, '.hgrc'))
1133 return path
1164 return path
1134
1165
1135 def parse_patch_output(output_line):
1166 def parse_patch_output(output_line):
1136 """parses the output produced by patch and returns the file name"""
1167 """parses the output produced by patch and returns the file name"""
1137 pf = output_line[14:]
1168 pf = output_line[14:]
1138 if pf[0] == '`':
1169 if pf[0] == '`':
1139 pf = pf[1:-1] # Remove the quotes
1170 pf = pf[1:-1] # Remove the quotes
1140 return pf
1171 return pf
1141
1172
1142 def sshargs(sshcmd, host, user, port):
1173 def sshargs(sshcmd, host, user, port):
1143 '''Build argument list for ssh or Plink'''
1174 '''Build argument list for ssh or Plink'''
1144 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1175 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1145 args = user and ("%s@%s" % (user, host)) or host
1176 args = user and ("%s@%s" % (user, host)) or host
1146 return port and ("%s %s %s" % (args, pflag, port)) or args
1177 return port and ("%s %s %s" % (args, pflag, port)) or args
1147
1178
1148 def testpid(pid):
1179 def testpid(pid):
1149 '''return False if pid dead, True if running or not known'''
1180 '''return False if pid dead, True if running or not known'''
1150 return True
1181 return True
1151
1182
1152 def set_flags(f, l, x):
1183 def set_flags(f, l, x):
1153 pass
1184 pass
1154
1185
1155 def set_binary(fd):
1186 def set_binary(fd):
1156 # When run without console, pipes may expose invalid
1187 # When run without console, pipes may expose invalid
1157 # fileno(), usually set to -1.
1188 # fileno(), usually set to -1.
1158 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1189 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1159 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1190 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1160
1191
1161 def pconvert(path):
1192 def pconvert(path):
1162 return '/'.join(splitpath(path))
1193 return '/'.join(splitpath(path))
1163
1194
1164 def localpath(path):
1195 def localpath(path):
1165 return path.replace('/', '\\')
1196 return path.replace('/', '\\')
1166
1197
1167 def normpath(path):
1198 def normpath(path):
1168 return pconvert(os.path.normpath(path))
1199 return pconvert(os.path.normpath(path))
1169
1200
1170 makelock = _makelock_file
1201 makelock = _makelock_file
1171 readlock = _readlock_file
1202 readlock = _readlock_file
1172
1203
1173 def samestat(s1, s2):
1204 def samestat(s1, s2):
1174 return False
1205 return False
1175
1206
1176 # A sequence of backslashes is special iff it precedes a double quote:
1207 # A sequence of backslashes is special iff it precedes a double quote:
1177 # - if there's an even number of backslashes, the double quote is not
1208 # - if there's an even number of backslashes, the double quote is not
1178 # quoted (i.e. it ends the quoted region)
1209 # quoted (i.e. it ends the quoted region)
1179 # - if there's an odd number of backslashes, the double quote is quoted
1210 # - if there's an odd number of backslashes, the double quote is quoted
1180 # - in both cases, every pair of backslashes is unquoted into a single
1211 # - in both cases, every pair of backslashes is unquoted into a single
1181 # backslash
1212 # backslash
1182 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1213 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1183 # So, to quote a string, we must surround it in double quotes, double
1214 # So, to quote a string, we must surround it in double quotes, double
1184 # the number of backslashes that preceed double quotes and add another
1215 # the number of backslashes that preceed double quotes and add another
1185 # backslash before every double quote (being careful with the double
1216 # backslash before every double quote (being careful with the double
1186 # quote we've appended to the end)
1217 # quote we've appended to the end)
1187 _quotere = None
1218 _quotere = None
1188 def shellquote(s):
1219 def shellquote(s):
1189 global _quotere
1220 global _quotere
1190 if _quotere is None:
1221 if _quotere is None:
1191 _quotere = re.compile(r'(\\*)("|\\$)')
1222 _quotere = re.compile(r'(\\*)("|\\$)')
1192 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1223 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1193
1224
1194 def quotecommand(cmd):
1225 def quotecommand(cmd):
1195 """Build a command string suitable for os.popen* calls."""
1226 """Build a command string suitable for os.popen* calls."""
1196 # The extra quotes are needed because popen* runs the command
1227 # The extra quotes are needed because popen* runs the command
1197 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1228 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1198 return '"' + cmd + '"'
1229 return '"' + cmd + '"'
1199
1230
1200 def popen(command, mode='r'):
1231 def popen(command, mode='r'):
1201 # Work around "popen spawned process may not write to stdout
1232 # Work around "popen spawned process may not write to stdout
1202 # under windows"
1233 # under windows"
1203 # http://bugs.python.org/issue1366
1234 # http://bugs.python.org/issue1366
1204 command += " 2> %s" % nulldev
1235 command += " 2> %s" % nulldev
1205 return os.popen(quotecommand(command), mode)
1236 return os.popen(quotecommand(command), mode)
1206
1237
1207 def explain_exit(code):
1238 def explain_exit(code):
1208 return _("exited with status %d") % code, code
1239 return _("exited with status %d") % code, code
1209
1240
1210 # if you change this stub into a real check, please try to implement the
1241 # if you change this stub into a real check, please try to implement the
1211 # username and groupname functions above, too.
1242 # username and groupname functions above, too.
1212 def isowner(fp, st=None):
1243 def isowner(fp, st=None):
1213 return True
1244 return True
1214
1245
1215 def find_in_path(name, path, default=None):
1246 def find_in_path(name, path, default=None):
1216 '''find name in search path. path can be string (will be split
1247 '''find name in search path. path can be string (will be split
1217 with os.pathsep), or iterable thing that returns strings. if name
1248 with os.pathsep), or iterable thing that returns strings. if name
1218 found, return path to name. else return default. name is looked up
1249 found, return path to name. else return default. name is looked up
1219 using cmd.exe rules, using PATHEXT.'''
1250 using cmd.exe rules, using PATHEXT.'''
1220 if isinstance(path, str):
1251 if isinstance(path, str):
1221 path = path.split(os.pathsep)
1252 path = path.split(os.pathsep)
1222
1253
1223 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1254 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1224 pathext = pathext.lower().split(os.pathsep)
1255 pathext = pathext.lower().split(os.pathsep)
1225 isexec = os.path.splitext(name)[1].lower() in pathext
1256 isexec = os.path.splitext(name)[1].lower() in pathext
1226
1257
1227 for p in path:
1258 for p in path:
1228 p_name = os.path.join(p, name)
1259 p_name = os.path.join(p, name)
1229
1260
1230 if isexec and os.path.exists(p_name):
1261 if isexec and os.path.exists(p_name):
1231 return p_name
1262 return p_name
1232
1263
1233 for ext in pathext:
1264 for ext in pathext:
1234 p_name_ext = p_name + ext
1265 p_name_ext = p_name + ext
1235 if os.path.exists(p_name_ext):
1266 if os.path.exists(p_name_ext):
1236 return p_name_ext
1267 return p_name_ext
1237 return default
1268 return default
1238
1269
1239 def set_signal_handler():
1270 def set_signal_handler():
1240 try:
1271 try:
1241 set_signal_handler_win32()
1272 set_signal_handler_win32()
1242 except NameError:
1273 except NameError:
1243 pass
1274 pass
1244
1275
1245 try:
1276 try:
1246 # override functions with win32 versions if possible
1277 # override functions with win32 versions if possible
1247 from util_win32 import *
1278 from util_win32 import *
1248 if not _is_win_9x():
1279 if not _is_win_9x():
1249 posixfile = posixfile_nt
1280 posixfile = posixfile_nt
1250 except ImportError:
1281 except ImportError:
1251 pass
1282 pass
1252
1283
1253 else:
1284 else:
1254 nulldev = '/dev/null'
1285 nulldev = '/dev/null'
1255
1286
1256 def rcfiles(path):
1287 def rcfiles(path):
1257 rcs = [os.path.join(path, 'hgrc')]
1288 rcs = [os.path.join(path, 'hgrc')]
1258 rcdir = os.path.join(path, 'hgrc.d')
1289 rcdir = os.path.join(path, 'hgrc.d')
1259 try:
1290 try:
1260 rcs.extend([os.path.join(rcdir, f)
1291 rcs.extend([os.path.join(rcdir, f)
1261 for f, kind in osutil.listdir(rcdir)
1292 for f, kind in osutil.listdir(rcdir)
1262 if f.endswith(".rc")])
1293 if f.endswith(".rc")])
1263 except OSError:
1294 except OSError:
1264 pass
1295 pass
1265 return rcs
1296 return rcs
1266
1297
1267 def system_rcpath():
1298 def system_rcpath():
1268 path = []
1299 path = []
1269 # old mod_python does not set sys.argv
1300 # old mod_python does not set sys.argv
1270 if len(getattr(sys, 'argv', [])) > 0:
1301 if len(getattr(sys, 'argv', [])) > 0:
1271 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1302 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1272 '/../etc/mercurial'))
1303 '/../etc/mercurial'))
1273 path.extend(rcfiles('/etc/mercurial'))
1304 path.extend(rcfiles('/etc/mercurial'))
1274 return path
1305 return path
1275
1306
1276 def user_rcpath():
1307 def user_rcpath():
1277 return [os.path.expanduser('~/.hgrc')]
1308 return [os.path.expanduser('~/.hgrc')]
1278
1309
1279 def parse_patch_output(output_line):
1310 def parse_patch_output(output_line):
1280 """parses the output produced by patch and returns the file name"""
1311 """parses the output produced by patch and returns the file name"""
1281 pf = output_line[14:]
1312 pf = output_line[14:]
1282 if os.sys.platform == 'OpenVMS':
1313 if os.sys.platform == 'OpenVMS':
1283 if pf[0] == '`':
1314 if pf[0] == '`':
1284 pf = pf[1:-1] # Remove the quotes
1315 pf = pf[1:-1] # Remove the quotes
1285 else:
1316 else:
1286 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1317 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1287 pf = pf[1:-1] # Remove the quotes
1318 pf = pf[1:-1] # Remove the quotes
1288 return pf
1319 return pf
1289
1320
1290 def sshargs(sshcmd, host, user, port):
1321 def sshargs(sshcmd, host, user, port):
1291 '''Build argument list for ssh'''
1322 '''Build argument list for ssh'''
1292 args = user and ("%s@%s" % (user, host)) or host
1323 args = user and ("%s@%s" % (user, host)) or host
1293 return port and ("%s -p %s" % (args, port)) or args
1324 return port and ("%s -p %s" % (args, port)) or args
1294
1325
1295 def is_exec(f):
1326 def is_exec(f):
1296 """check whether a file is executable"""
1327 """check whether a file is executable"""
1297 return (os.lstat(f).st_mode & 0100 != 0)
1328 return (os.lstat(f).st_mode & 0100 != 0)
1298
1329
1299 def set_flags(f, l, x):
1330 def set_flags(f, l, x):
1300 s = os.lstat(f).st_mode
1331 s = os.lstat(f).st_mode
1301 if l:
1332 if l:
1302 if not stat.S_ISLNK(s):
1333 if not stat.S_ISLNK(s):
1303 # switch file to link
1334 # switch file to link
1304 data = file(f).read()
1335 data = file(f).read()
1305 os.unlink(f)
1336 os.unlink(f)
1306 try:
1337 try:
1307 os.symlink(data, f)
1338 os.symlink(data, f)
1308 except:
1339 except:
1309 # failed to make a link, rewrite file
1340 # failed to make a link, rewrite file
1310 file(f, "w").write(data)
1341 file(f, "w").write(data)
1311 # no chmod needed at this point
1342 # no chmod needed at this point
1312 return
1343 return
1313 if stat.S_ISLNK(s):
1344 if stat.S_ISLNK(s):
1314 # switch link to file
1345 # switch link to file
1315 data = os.readlink(f)
1346 data = os.readlink(f)
1316 os.unlink(f)
1347 os.unlink(f)
1317 file(f, "w").write(data)
1348 file(f, "w").write(data)
1318 s = 0666 & ~_umask # avoid restatting for chmod
1349 s = 0666 & ~_umask # avoid restatting for chmod
1319
1350
1320 sx = s & 0100
1351 sx = s & 0100
1321 if x and not sx:
1352 if x and not sx:
1322 # Turn on +x for every +r bit when making a file executable
1353 # Turn on +x for every +r bit when making a file executable
1323 # and obey umask.
1354 # and obey umask.
1324 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1355 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1325 elif not x and sx:
1356 elif not x and sx:
1326 # Turn off all +x bits
1357 # Turn off all +x bits
1327 os.chmod(f, s & 0666)
1358 os.chmod(f, s & 0666)
1328
1359
1329 def set_binary(fd):
1360 def set_binary(fd):
1330 pass
1361 pass
1331
1362
1332 def pconvert(path):
1363 def pconvert(path):
1333 return path
1364 return path
1334
1365
1335 def localpath(path):
1366 def localpath(path):
1336 return path
1367 return path
1337
1368
1338 normpath = os.path.normpath
1369 normpath = os.path.normpath
1339 samestat = os.path.samestat
1370 samestat = os.path.samestat
1340
1371
1341 def makelock(info, pathname):
1372 def makelock(info, pathname):
1342 try:
1373 try:
1343 os.symlink(info, pathname)
1374 os.symlink(info, pathname)
1344 except OSError, why:
1375 except OSError, why:
1345 if why.errno == errno.EEXIST:
1376 if why.errno == errno.EEXIST:
1346 raise
1377 raise
1347 else:
1378 else:
1348 _makelock_file(info, pathname)
1379 _makelock_file(info, pathname)
1349
1380
1350 def readlock(pathname):
1381 def readlock(pathname):
1351 try:
1382 try:
1352 return os.readlink(pathname)
1383 return os.readlink(pathname)
1353 except OSError, why:
1384 except OSError, why:
1354 if why.errno in (errno.EINVAL, errno.ENOSYS):
1385 if why.errno in (errno.EINVAL, errno.ENOSYS):
1355 return _readlock_file(pathname)
1386 return _readlock_file(pathname)
1356 else:
1387 else:
1357 raise
1388 raise
1358
1389
1359 def shellquote(s):
1390 def shellquote(s):
1360 if os.sys.platform == 'OpenVMS':
1391 if os.sys.platform == 'OpenVMS':
1361 return '"%s"' % s
1392 return '"%s"' % s
1362 else:
1393 else:
1363 return "'%s'" % s.replace("'", "'\\''")
1394 return "'%s'" % s.replace("'", "'\\''")
1364
1395
1365 def quotecommand(cmd):
1396 def quotecommand(cmd):
1366 return cmd
1397 return cmd
1367
1398
1368 def popen(command, mode='r'):
1399 def popen(command, mode='r'):
1369 return os.popen(command, mode)
1400 return os.popen(command, mode)
1370
1401
1371 def testpid(pid):
1402 def testpid(pid):
1372 '''return False if pid dead, True if running or not sure'''
1403 '''return False if pid dead, True if running or not sure'''
1373 if os.sys.platform == 'OpenVMS':
1404 if os.sys.platform == 'OpenVMS':
1374 return True
1405 return True
1375 try:
1406 try:
1376 os.kill(pid, 0)
1407 os.kill(pid, 0)
1377 return True
1408 return True
1378 except OSError, inst:
1409 except OSError, inst:
1379 return inst.errno != errno.ESRCH
1410 return inst.errno != errno.ESRCH
1380
1411
1381 def explain_exit(code):
1412 def explain_exit(code):
1382 """return a 2-tuple (desc, code) describing a process's status"""
1413 """return a 2-tuple (desc, code) describing a process's status"""
1383 if os.WIFEXITED(code):
1414 if os.WIFEXITED(code):
1384 val = os.WEXITSTATUS(code)
1415 val = os.WEXITSTATUS(code)
1385 return _("exited with status %d") % val, val
1416 return _("exited with status %d") % val, val
1386 elif os.WIFSIGNALED(code):
1417 elif os.WIFSIGNALED(code):
1387 val = os.WTERMSIG(code)
1418 val = os.WTERMSIG(code)
1388 return _("killed by signal %d") % val, val
1419 return _("killed by signal %d") % val, val
1389 elif os.WIFSTOPPED(code):
1420 elif os.WIFSTOPPED(code):
1390 val = os.WSTOPSIG(code)
1421 val = os.WSTOPSIG(code)
1391 return _("stopped by signal %d") % val, val
1422 return _("stopped by signal %d") % val, val
1392 raise ValueError(_("invalid exit code"))
1423 raise ValueError(_("invalid exit code"))
1393
1424
1394 def isowner(fp, st=None):
1425 def isowner(fp, st=None):
1395 """Return True if the file object f belongs to the current user.
1426 """Return True if the file object f belongs to the current user.
1396
1427
1397 The return value of a util.fstat(f) may be passed as the st argument.
1428 The return value of a util.fstat(f) may be passed as the st argument.
1398 """
1429 """
1399 if st is None:
1430 if st is None:
1400 st = fstat(fp)
1431 st = fstat(fp)
1401 return st.st_uid == os.getuid()
1432 return st.st_uid == os.getuid()
1402
1433
1403 def find_in_path(name, path, default=None):
1434 def find_in_path(name, path, default=None):
1404 '''find name in search path. path can be string (will be split
1435 '''find name in search path. path can be string (will be split
1405 with os.pathsep), or iterable thing that returns strings. if name
1436 with os.pathsep), or iterable thing that returns strings. if name
1406 found, return path to name. else return default.'''
1437 found, return path to name. else return default.'''
1407 if isinstance(path, str):
1438 if isinstance(path, str):
1408 path = path.split(os.pathsep)
1439 path = path.split(os.pathsep)
1409 for p in path:
1440 for p in path:
1410 p_name = os.path.join(p, name)
1441 p_name = os.path.join(p, name)
1411 if os.path.exists(p_name):
1442 if os.path.exists(p_name):
1412 return p_name
1443 return p_name
1413 return default
1444 return default
1414
1445
1415 def set_signal_handler():
1446 def set_signal_handler():
1416 pass
1447 pass
1417
1448
1418 def find_exe(name, default=None):
1449 def find_exe(name, default=None):
1419 '''find path of an executable.
1450 '''find path of an executable.
1420 if name contains a path component, return it as is. otherwise,
1451 if name contains a path component, return it as is. otherwise,
1421 use normal executable search path.'''
1452 use normal executable search path.'''
1422
1453
1423 if os.sep in name or sys.platform == 'OpenVMS':
1454 if os.sep in name or sys.platform == 'OpenVMS':
1424 # don't check the executable bit. if the file isn't
1455 # don't check the executable bit. if the file isn't
1425 # executable, whoever tries to actually run it will give a
1456 # executable, whoever tries to actually run it will give a
1426 # much more useful error message.
1457 # much more useful error message.
1427 return name
1458 return name
1428 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1459 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1429
1460
1430 def mktempcopy(name, emptyok=False, createmode=None):
1461 def mktempcopy(name, emptyok=False, createmode=None):
1431 """Create a temporary file with the same contents from name
1462 """Create a temporary file with the same contents from name
1432
1463
1433 The permission bits are copied from the original file.
1464 The permission bits are copied from the original file.
1434
1465
1435 If the temporary file is going to be truncated immediately, you
1466 If the temporary file is going to be truncated immediately, you
1436 can use emptyok=True as an optimization.
1467 can use emptyok=True as an optimization.
1437
1468
1438 Returns the name of the temporary file.
1469 Returns the name of the temporary file.
1439 """
1470 """
1440 d, fn = os.path.split(name)
1471 d, fn = os.path.split(name)
1441 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1472 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1442 os.close(fd)
1473 os.close(fd)
1443 # Temporary files are created with mode 0600, which is usually not
1474 # Temporary files are created with mode 0600, which is usually not
1444 # what we want. If the original file already exists, just copy
1475 # what we want. If the original file already exists, just copy
1445 # its mode. Otherwise, manually obey umask.
1476 # its mode. Otherwise, manually obey umask.
1446 try:
1477 try:
1447 st_mode = os.lstat(name).st_mode & 0777
1478 st_mode = os.lstat(name).st_mode & 0777
1448 except OSError, inst:
1479 except OSError, inst:
1449 if inst.errno != errno.ENOENT:
1480 if inst.errno != errno.ENOENT:
1450 raise
1481 raise
1451 st_mode = createmode
1482 st_mode = createmode
1452 if st_mode is None:
1483 if st_mode is None:
1453 st_mode = ~_umask
1484 st_mode = ~_umask
1454 st_mode &= 0666
1485 st_mode &= 0666
1455 os.chmod(temp, st_mode)
1486 os.chmod(temp, st_mode)
1456 if emptyok:
1487 if emptyok:
1457 return temp
1488 return temp
1458 try:
1489 try:
1459 try:
1490 try:
1460 ifp = posixfile(name, "rb")
1491 ifp = posixfile(name, "rb")
1461 except IOError, inst:
1492 except IOError, inst:
1462 if inst.errno == errno.ENOENT:
1493 if inst.errno == errno.ENOENT:
1463 return temp
1494 return temp
1464 if not getattr(inst, 'filename', None):
1495 if not getattr(inst, 'filename', None):
1465 inst.filename = name
1496 inst.filename = name
1466 raise
1497 raise
1467 ofp = posixfile(temp, "wb")
1498 ofp = posixfile(temp, "wb")
1468 for chunk in filechunkiter(ifp):
1499 for chunk in filechunkiter(ifp):
1469 ofp.write(chunk)
1500 ofp.write(chunk)
1470 ifp.close()
1501 ifp.close()
1471 ofp.close()
1502 ofp.close()
1472 except:
1503 except:
1473 try: os.unlink(temp)
1504 try: os.unlink(temp)
1474 except: pass
1505 except: pass
1475 raise
1506 raise
1476 return temp
1507 return temp
1477
1508
1478 class atomictempfile(posixfile):
1509 class atomictempfile(posixfile):
1479 """file-like object that atomically updates a file
1510 """file-like object that atomically updates a file
1480
1511
1481 All writes will be redirected to a temporary copy of the original
1512 All writes will be redirected to a temporary copy of the original
1482 file. When rename is called, the copy is renamed to the original
1513 file. When rename is called, the copy is renamed to the original
1483 name, making the changes visible.
1514 name, making the changes visible.
1484 """
1515 """
1485 def __init__(self, name, mode, createmode):
1516 def __init__(self, name, mode, createmode):
1486 self.__name = name
1517 self.__name = name
1487 self.temp = mktempcopy(name, emptyok=('w' in mode),
1518 self.temp = mktempcopy(name, emptyok=('w' in mode),
1488 createmode=createmode)
1519 createmode=createmode)
1489 posixfile.__init__(self, self.temp, mode)
1520 posixfile.__init__(self, self.temp, mode)
1490
1521
1491 def rename(self):
1522 def rename(self):
1492 if not self.closed:
1523 if not self.closed:
1493 posixfile.close(self)
1524 posixfile.close(self)
1494 rename(self.temp, localpath(self.__name))
1525 rename(self.temp, localpath(self.__name))
1495
1526
1496 def __del__(self):
1527 def __del__(self):
1497 if not self.closed:
1528 if not self.closed:
1498 try:
1529 try:
1499 os.unlink(self.temp)
1530 os.unlink(self.temp)
1500 except: pass
1531 except: pass
1501 posixfile.close(self)
1532 posixfile.close(self)
1502
1533
1503 def makedirs(name, mode=None):
1534 def makedirs(name, mode=None):
1504 """recursive directory creation with parent mode inheritance"""
1535 """recursive directory creation with parent mode inheritance"""
1505 try:
1536 try:
1506 os.mkdir(name)
1537 os.mkdir(name)
1507 if mode is not None:
1538 if mode is not None:
1508 os.chmod(name, mode)
1539 os.chmod(name, mode)
1509 return
1540 return
1510 except OSError, err:
1541 except OSError, err:
1511 if err.errno == errno.EEXIST:
1542 if err.errno == errno.EEXIST:
1512 return
1543 return
1513 if err.errno != errno.ENOENT:
1544 if err.errno != errno.ENOENT:
1514 raise
1545 raise
1515 parent = os.path.abspath(os.path.dirname(name))
1546 parent = os.path.abspath(os.path.dirname(name))
1516 makedirs(parent, mode)
1547 makedirs(parent, mode)
1517 makedirs(name, mode)
1548 makedirs(name, mode)
1518
1549
1519 class opener(object):
1550 class opener(object):
1520 """Open files relative to a base directory
1551 """Open files relative to a base directory
1521
1552
1522 This class is used to hide the details of COW semantics and
1553 This class is used to hide the details of COW semantics and
1523 remote file access from higher level code.
1554 remote file access from higher level code.
1524 """
1555 """
1525 def __init__(self, base, audit=True):
1556 def __init__(self, base, audit=True):
1526 self.base = base
1557 self.base = base
1527 if audit:
1558 if audit:
1528 self.audit_path = path_auditor(base)
1559 self.audit_path = path_auditor(base)
1529 else:
1560 else:
1530 self.audit_path = always
1561 self.audit_path = always
1531 self.createmode = None
1562 self.createmode = None
1532
1563
1533 def __getattr__(self, name):
1564 def __getattr__(self, name):
1534 if name == '_can_symlink':
1565 if name == '_can_symlink':
1535 self._can_symlink = checklink(self.base)
1566 self._can_symlink = checklink(self.base)
1536 return self._can_symlink
1567 return self._can_symlink
1537 raise AttributeError(name)
1568 raise AttributeError(name)
1538
1569
1539 def _fixfilemode(self, name):
1570 def _fixfilemode(self, name):
1540 if self.createmode is None:
1571 if self.createmode is None:
1541 return
1572 return
1542 os.chmod(name, self.createmode & 0666)
1573 os.chmod(name, self.createmode & 0666)
1543
1574
1544 def __call__(self, path, mode="r", text=False, atomictemp=False):
1575 def __call__(self, path, mode="r", text=False, atomictemp=False):
1545 self.audit_path(path)
1576 self.audit_path(path)
1546 f = os.path.join(self.base, path)
1577 f = os.path.join(self.base, path)
1547
1578
1548 if not text and "b" not in mode:
1579 if not text and "b" not in mode:
1549 mode += "b" # for that other OS
1580 mode += "b" # for that other OS
1550
1581
1551 nlink = -1
1582 nlink = -1
1552 if mode not in ("r", "rb"):
1583 if mode not in ("r", "rb"):
1553 try:
1584 try:
1554 nlink = nlinks(f)
1585 nlink = nlinks(f)
1555 except OSError:
1586 except OSError:
1556 nlink = 0
1587 nlink = 0
1557 d = os.path.dirname(f)
1588 d = os.path.dirname(f)
1558 if not os.path.isdir(d):
1589 if not os.path.isdir(d):
1559 makedirs(d, self.createmode)
1590 makedirs(d, self.createmode)
1560 if atomictemp:
1591 if atomictemp:
1561 return atomictempfile(f, mode, self.createmode)
1592 return atomictempfile(f, mode, self.createmode)
1562 if nlink > 1:
1593 if nlink > 1:
1563 rename(mktempcopy(f), f)
1594 rename(mktempcopy(f), f)
1564 fp = posixfile(f, mode)
1595 fp = posixfile(f, mode)
1565 if nlink == 0:
1596 if nlink == 0:
1566 self._fixfilemode(f)
1597 self._fixfilemode(f)
1567 return fp
1598 return fp
1568
1599
1569 def symlink(self, src, dst):
1600 def symlink(self, src, dst):
1570 self.audit_path(dst)
1601 self.audit_path(dst)
1571 linkname = os.path.join(self.base, dst)
1602 linkname = os.path.join(self.base, dst)
1572 try:
1603 try:
1573 os.unlink(linkname)
1604 os.unlink(linkname)
1574 except OSError:
1605 except OSError:
1575 pass
1606 pass
1576
1607
1577 dirname = os.path.dirname(linkname)
1608 dirname = os.path.dirname(linkname)
1578 if not os.path.exists(dirname):
1609 if not os.path.exists(dirname):
1579 makedirs(dirname, self.createmode)
1610 makedirs(dirname, self.createmode)
1580
1611
1581 if self._can_symlink:
1612 if self._can_symlink:
1582 try:
1613 try:
1583 os.symlink(src, linkname)
1614 os.symlink(src, linkname)
1584 except OSError, err:
1615 except OSError, err:
1585 raise OSError(err.errno, _('could not symlink to %r: %s') %
1616 raise OSError(err.errno, _('could not symlink to %r: %s') %
1586 (src, err.strerror), linkname)
1617 (src, err.strerror), linkname)
1587 else:
1618 else:
1588 f = self(dst, "w")
1619 f = self(dst, "w")
1589 f.write(src)
1620 f.write(src)
1590 f.close()
1621 f.close()
1591 self._fixfilemode(dst)
1622 self._fixfilemode(dst)
1592
1623
1593 class chunkbuffer(object):
1624 class chunkbuffer(object):
1594 """Allow arbitrary sized chunks of data to be efficiently read from an
1625 """Allow arbitrary sized chunks of data to be efficiently read from an
1595 iterator over chunks of arbitrary size."""
1626 iterator over chunks of arbitrary size."""
1596
1627
1597 def __init__(self, in_iter):
1628 def __init__(self, in_iter):
1598 """in_iter is the iterator that's iterating over the input chunks.
1629 """in_iter is the iterator that's iterating over the input chunks.
1599 targetsize is how big a buffer to try to maintain."""
1630 targetsize is how big a buffer to try to maintain."""
1600 self.iter = iter(in_iter)
1631 self.iter = iter(in_iter)
1601 self.buf = ''
1632 self.buf = ''
1602 self.targetsize = 2**16
1633 self.targetsize = 2**16
1603
1634
1604 def read(self, l):
1635 def read(self, l):
1605 """Read L bytes of data from the iterator of chunks of data.
1636 """Read L bytes of data from the iterator of chunks of data.
1606 Returns less than L bytes if the iterator runs dry."""
1637 Returns less than L bytes if the iterator runs dry."""
1607 if l > len(self.buf) and self.iter:
1638 if l > len(self.buf) and self.iter:
1608 # Clamp to a multiple of self.targetsize
1639 # Clamp to a multiple of self.targetsize
1609 targetsize = max(l, self.targetsize)
1640 targetsize = max(l, self.targetsize)
1610 collector = cStringIO.StringIO()
1641 collector = cStringIO.StringIO()
1611 collector.write(self.buf)
1642 collector.write(self.buf)
1612 collected = len(self.buf)
1643 collected = len(self.buf)
1613 for chunk in self.iter:
1644 for chunk in self.iter:
1614 collector.write(chunk)
1645 collector.write(chunk)
1615 collected += len(chunk)
1646 collected += len(chunk)
1616 if collected >= targetsize:
1647 if collected >= targetsize:
1617 break
1648 break
1618 if collected < targetsize:
1649 if collected < targetsize:
1619 self.iter = False
1650 self.iter = False
1620 self.buf = collector.getvalue()
1651 self.buf = collector.getvalue()
1621 if len(self.buf) == l:
1652 if len(self.buf) == l:
1622 s, self.buf = str(self.buf), ''
1653 s, self.buf = str(self.buf), ''
1623 else:
1654 else:
1624 s, self.buf = self.buf[:l], buffer(self.buf, l)
1655 s, self.buf = self.buf[:l], buffer(self.buf, l)
1625 return s
1656 return s
1626
1657
1627 def filechunkiter(f, size=65536, limit=None):
1658 def filechunkiter(f, size=65536, limit=None):
1628 """Create a generator that produces the data in the file size
1659 """Create a generator that produces the data in the file size
1629 (default 65536) bytes at a time, up to optional limit (default is
1660 (default 65536) bytes at a time, up to optional limit (default is
1630 to read all data). Chunks may be less than size bytes if the
1661 to read all data). Chunks may be less than size bytes if the
1631 chunk is the last chunk in the file, or the file is a socket or
1662 chunk is the last chunk in the file, or the file is a socket or
1632 some other type of file that sometimes reads less data than is
1663 some other type of file that sometimes reads less data than is
1633 requested."""
1664 requested."""
1634 assert size >= 0
1665 assert size >= 0
1635 assert limit is None or limit >= 0
1666 assert limit is None or limit >= 0
1636 while True:
1667 while True:
1637 if limit is None: nbytes = size
1668 if limit is None: nbytes = size
1638 else: nbytes = min(limit, size)
1669 else: nbytes = min(limit, size)
1639 s = nbytes and f.read(nbytes)
1670 s = nbytes and f.read(nbytes)
1640 if not s: break
1671 if not s: break
1641 if limit: limit -= len(s)
1672 if limit: limit -= len(s)
1642 yield s
1673 yield s
1643
1674
1644 def makedate():
1675 def makedate():
1645 lt = time.localtime()
1676 lt = time.localtime()
1646 if lt[8] == 1 and time.daylight:
1677 if lt[8] == 1 and time.daylight:
1647 tz = time.altzone
1678 tz = time.altzone
1648 else:
1679 else:
1649 tz = time.timezone
1680 tz = time.timezone
1650 return time.mktime(lt), tz
1681 return time.mktime(lt), tz
1651
1682
1652 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1683 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1653 """represent a (unixtime, offset) tuple as a localized time.
1684 """represent a (unixtime, offset) tuple as a localized time.
1654 unixtime is seconds since the epoch, and offset is the time zone's
1685 unixtime is seconds since the epoch, and offset is the time zone's
1655 number of seconds away from UTC. if timezone is false, do not
1686 number of seconds away from UTC. if timezone is false, do not
1656 append time zone to string."""
1687 append time zone to string."""
1657 t, tz = date or makedate()
1688 t, tz = date or makedate()
1658 if "%1" in format or "%2" in format:
1689 if "%1" in format or "%2" in format:
1659 sign = (tz > 0) and "-" or "+"
1690 sign = (tz > 0) and "-" or "+"
1660 minutes = abs(tz) / 60
1691 minutes = abs(tz) / 60
1661 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1692 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1662 format = format.replace("%2", "%02d" % (minutes % 60))
1693 format = format.replace("%2", "%02d" % (minutes % 60))
1663 s = time.strftime(format, time.gmtime(float(t) - tz))
1694 s = time.strftime(format, time.gmtime(float(t) - tz))
1664 return s
1695 return s
1665
1696
1666 def shortdate(date=None):
1697 def shortdate(date=None):
1667 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1698 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1668 return datestr(date, format='%Y-%m-%d')
1699 return datestr(date, format='%Y-%m-%d')
1669
1700
1670 def strdate(string, format, defaults=[]):
1701 def strdate(string, format, defaults=[]):
1671 """parse a localized time string and return a (unixtime, offset) tuple.
1702 """parse a localized time string and return a (unixtime, offset) tuple.
1672 if the string cannot be parsed, ValueError is raised."""
1703 if the string cannot be parsed, ValueError is raised."""
1673 def timezone(string):
1704 def timezone(string):
1674 tz = string.split()[-1]
1705 tz = string.split()[-1]
1675 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1706 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1676 sign = (tz[0] == "+") and 1 or -1
1707 sign = (tz[0] == "+") and 1 or -1
1677 hours = int(tz[1:3])
1708 hours = int(tz[1:3])
1678 minutes = int(tz[3:5])
1709 minutes = int(tz[3:5])
1679 return -sign * (hours * 60 + minutes) * 60
1710 return -sign * (hours * 60 + minutes) * 60
1680 if tz == "GMT" or tz == "UTC":
1711 if tz == "GMT" or tz == "UTC":
1681 return 0
1712 return 0
1682 return None
1713 return None
1683
1714
1684 # NOTE: unixtime = localunixtime + offset
1715 # NOTE: unixtime = localunixtime + offset
1685 offset, date = timezone(string), string
1716 offset, date = timezone(string), string
1686 if offset != None:
1717 if offset != None:
1687 date = " ".join(string.split()[:-1])
1718 date = " ".join(string.split()[:-1])
1688
1719
1689 # add missing elements from defaults
1720 # add missing elements from defaults
1690 for part in defaults:
1721 for part in defaults:
1691 found = [True for p in part if ("%"+p) in format]
1722 found = [True for p in part if ("%"+p) in format]
1692 if not found:
1723 if not found:
1693 date += "@" + defaults[part]
1724 date += "@" + defaults[part]
1694 format += "@%" + part[0]
1725 format += "@%" + part[0]
1695
1726
1696 timetuple = time.strptime(date, format)
1727 timetuple = time.strptime(date, format)
1697 localunixtime = int(calendar.timegm(timetuple))
1728 localunixtime = int(calendar.timegm(timetuple))
1698 if offset is None:
1729 if offset is None:
1699 # local timezone
1730 # local timezone
1700 unixtime = int(time.mktime(timetuple))
1731 unixtime = int(time.mktime(timetuple))
1701 offset = unixtime - localunixtime
1732 offset = unixtime - localunixtime
1702 else:
1733 else:
1703 unixtime = localunixtime + offset
1734 unixtime = localunixtime + offset
1704 return unixtime, offset
1735 return unixtime, offset
1705
1736
1706 def parsedate(date, formats=None, defaults=None):
1737 def parsedate(date, formats=None, defaults=None):
1707 """parse a localized date/time string and return a (unixtime, offset) tuple.
1738 """parse a localized date/time string and return a (unixtime, offset) tuple.
1708
1739
1709 The date may be a "unixtime offset" string or in one of the specified
1740 The date may be a "unixtime offset" string or in one of the specified
1710 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1741 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1711 """
1742 """
1712 if not date:
1743 if not date:
1713 return 0, 0
1744 return 0, 0
1714 if isinstance(date, tuple) and len(date) == 2:
1745 if isinstance(date, tuple) and len(date) == 2:
1715 return date
1746 return date
1716 if not formats:
1747 if not formats:
1717 formats = defaultdateformats
1748 formats = defaultdateformats
1718 date = date.strip()
1749 date = date.strip()
1719 try:
1750 try:
1720 when, offset = map(int, date.split(' '))
1751 when, offset = map(int, date.split(' '))
1721 except ValueError:
1752 except ValueError:
1722 # fill out defaults
1753 # fill out defaults
1723 if not defaults:
1754 if not defaults:
1724 defaults = {}
1755 defaults = {}
1725 now = makedate()
1756 now = makedate()
1726 for part in "d mb yY HI M S".split():
1757 for part in "d mb yY HI M S".split():
1727 if part not in defaults:
1758 if part not in defaults:
1728 if part[0] in "HMS":
1759 if part[0] in "HMS":
1729 defaults[part] = "00"
1760 defaults[part] = "00"
1730 else:
1761 else:
1731 defaults[part] = datestr(now, "%" + part[0])
1762 defaults[part] = datestr(now, "%" + part[0])
1732
1763
1733 for format in formats:
1764 for format in formats:
1734 try:
1765 try:
1735 when, offset = strdate(date, format, defaults)
1766 when, offset = strdate(date, format, defaults)
1736 except (ValueError, OverflowError):
1767 except (ValueError, OverflowError):
1737 pass
1768 pass
1738 else:
1769 else:
1739 break
1770 break
1740 else:
1771 else:
1741 raise Abort(_('invalid date: %r ') % date)
1772 raise Abort(_('invalid date: %r ') % date)
1742 # validate explicit (probably user-specified) date and
1773 # validate explicit (probably user-specified) date and
1743 # time zone offset. values must fit in signed 32 bits for
1774 # time zone offset. values must fit in signed 32 bits for
1744 # current 32-bit linux runtimes. timezones go from UTC-12
1775 # current 32-bit linux runtimes. timezones go from UTC-12
1745 # to UTC+14
1776 # to UTC+14
1746 if abs(when) > 0x7fffffff:
1777 if abs(when) > 0x7fffffff:
1747 raise Abort(_('date exceeds 32 bits: %d') % when)
1778 raise Abort(_('date exceeds 32 bits: %d') % when)
1748 if offset < -50400 or offset > 43200:
1779 if offset < -50400 or offset > 43200:
1749 raise Abort(_('impossible time zone offset: %d') % offset)
1780 raise Abort(_('impossible time zone offset: %d') % offset)
1750 return when, offset
1781 return when, offset
1751
1782
1752 def matchdate(date):
1783 def matchdate(date):
1753 """Return a function that matches a given date match specifier
1784 """Return a function that matches a given date match specifier
1754
1785
1755 Formats include:
1786 Formats include:
1756
1787
1757 '{date}' match a given date to the accuracy provided
1788 '{date}' match a given date to the accuracy provided
1758
1789
1759 '<{date}' on or before a given date
1790 '<{date}' on or before a given date
1760
1791
1761 '>{date}' on or after a given date
1792 '>{date}' on or after a given date
1762
1793
1763 """
1794 """
1764
1795
1765 def lower(date):
1796 def lower(date):
1766 d = dict(mb="1", d="1")
1797 d = dict(mb="1", d="1")
1767 return parsedate(date, extendeddateformats, d)[0]
1798 return parsedate(date, extendeddateformats, d)[0]
1768
1799
1769 def upper(date):
1800 def upper(date):
1770 d = dict(mb="12", HI="23", M="59", S="59")
1801 d = dict(mb="12", HI="23", M="59", S="59")
1771 for days in "31 30 29".split():
1802 for days in "31 30 29".split():
1772 try:
1803 try:
1773 d["d"] = days
1804 d["d"] = days
1774 return parsedate(date, extendeddateformats, d)[0]
1805 return parsedate(date, extendeddateformats, d)[0]
1775 except:
1806 except:
1776 pass
1807 pass
1777 d["d"] = "28"
1808 d["d"] = "28"
1778 return parsedate(date, extendeddateformats, d)[0]
1809 return parsedate(date, extendeddateformats, d)[0]
1779
1810
1780 if date[0] == "<":
1811 if date[0] == "<":
1781 when = upper(date[1:])
1812 when = upper(date[1:])
1782 return lambda x: x <= when
1813 return lambda x: x <= when
1783 elif date[0] == ">":
1814 elif date[0] == ">":
1784 when = lower(date[1:])
1815 when = lower(date[1:])
1785 return lambda x: x >= when
1816 return lambda x: x >= when
1786 elif date[0] == "-":
1817 elif date[0] == "-":
1787 try:
1818 try:
1788 days = int(date[1:])
1819 days = int(date[1:])
1789 except ValueError:
1820 except ValueError:
1790 raise Abort(_("invalid day spec: %s") % date[1:])
1821 raise Abort(_("invalid day spec: %s") % date[1:])
1791 when = makedate()[0] - days * 3600 * 24
1822 when = makedate()[0] - days * 3600 * 24
1792 return lambda x: x >= when
1823 return lambda x: x >= when
1793 elif " to " in date:
1824 elif " to " in date:
1794 a, b = date.split(" to ")
1825 a, b = date.split(" to ")
1795 start, stop = lower(a), upper(b)
1826 start, stop = lower(a), upper(b)
1796 return lambda x: x >= start and x <= stop
1827 return lambda x: x >= start and x <= stop
1797 else:
1828 else:
1798 start, stop = lower(date), upper(date)
1829 start, stop = lower(date), upper(date)
1799 return lambda x: x >= start and x <= stop
1830 return lambda x: x >= start and x <= stop
1800
1831
1801 def shortuser(user):
1832 def shortuser(user):
1802 """Return a short representation of a user name or email address."""
1833 """Return a short representation of a user name or email address."""
1803 f = user.find('@')
1834 f = user.find('@')
1804 if f >= 0:
1835 if f >= 0:
1805 user = user[:f]
1836 user = user[:f]
1806 f = user.find('<')
1837 f = user.find('<')
1807 if f >= 0:
1838 if f >= 0:
1808 user = user[f+1:]
1839 user = user[f+1:]
1809 f = user.find(' ')
1840 f = user.find(' ')
1810 if f >= 0:
1841 if f >= 0:
1811 user = user[:f]
1842 user = user[:f]
1812 f = user.find('.')
1843 f = user.find('.')
1813 if f >= 0:
1844 if f >= 0:
1814 user = user[:f]
1845 user = user[:f]
1815 return user
1846 return user
1816
1847
1817 def email(author):
1848 def email(author):
1818 '''get email of author.'''
1849 '''get email of author.'''
1819 r = author.find('>')
1850 r = author.find('>')
1820 if r == -1: r = None
1851 if r == -1: r = None
1821 return author[author.find('<')+1:r]
1852 return author[author.find('<')+1:r]
1822
1853
1823 def ellipsis(text, maxlength=400):
1854 def ellipsis(text, maxlength=400):
1824 """Trim string to at most maxlength (default: 400) characters."""
1855 """Trim string to at most maxlength (default: 400) characters."""
1825 if len(text) <= maxlength:
1856 if len(text) <= maxlength:
1826 return text
1857 return text
1827 else:
1858 else:
1828 return "%s..." % (text[:maxlength-3])
1859 return "%s..." % (text[:maxlength-3])
1829
1860
1830 def walkrepos(path, followsym=False, seen_dirs=None):
1861 def walkrepos(path, followsym=False, seen_dirs=None):
1831 '''yield every hg repository under path, recursively.'''
1862 '''yield every hg repository under path, recursively.'''
1832 def errhandler(err):
1863 def errhandler(err):
1833 if err.filename == path:
1864 if err.filename == path:
1834 raise err
1865 raise err
1835 if followsym and hasattr(os.path, 'samestat'):
1866 if followsym and hasattr(os.path, 'samestat'):
1836 def _add_dir_if_not_there(dirlst, dirname):
1867 def _add_dir_if_not_there(dirlst, dirname):
1837 match = False
1868 match = False
1838 samestat = os.path.samestat
1869 samestat = os.path.samestat
1839 dirstat = os.stat(dirname)
1870 dirstat = os.stat(dirname)
1840 for lstdirstat in dirlst:
1871 for lstdirstat in dirlst:
1841 if samestat(dirstat, lstdirstat):
1872 if samestat(dirstat, lstdirstat):
1842 match = True
1873 match = True
1843 break
1874 break
1844 if not match:
1875 if not match:
1845 dirlst.append(dirstat)
1876 dirlst.append(dirstat)
1846 return not match
1877 return not match
1847 else:
1878 else:
1848 followsym = False
1879 followsym = False
1849
1880
1850 if (seen_dirs is None) and followsym:
1881 if (seen_dirs is None) and followsym:
1851 seen_dirs = []
1882 seen_dirs = []
1852 _add_dir_if_not_there(seen_dirs, path)
1883 _add_dir_if_not_there(seen_dirs, path)
1853 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1884 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1854 if '.hg' in dirs:
1885 if '.hg' in dirs:
1855 dirs.remove('.hg') # don't recurse inside the .hg directory
1886 dirs.remove('.hg') # don't recurse inside the .hg directory
1856 yield root # found a repository
1887 yield root # found a repository
1857 qroot = os.path.join(root, '.hg', 'patches')
1888 qroot = os.path.join(root, '.hg', 'patches')
1858 if os.path.isdir(os.path.join(qroot, '.hg')):
1889 if os.path.isdir(os.path.join(qroot, '.hg')):
1859 yield qroot # we have a patch queue repo here
1890 yield qroot # we have a patch queue repo here
1860 elif followsym:
1891 elif followsym:
1861 newdirs = []
1892 newdirs = []
1862 for d in dirs:
1893 for d in dirs:
1863 fname = os.path.join(root, d)
1894 fname = os.path.join(root, d)
1864 if _add_dir_if_not_there(seen_dirs, fname):
1895 if _add_dir_if_not_there(seen_dirs, fname):
1865 if os.path.islink(fname):
1896 if os.path.islink(fname):
1866 for hgname in walkrepos(fname, True, seen_dirs):
1897 for hgname in walkrepos(fname, True, seen_dirs):
1867 yield hgname
1898 yield hgname
1868 else:
1899 else:
1869 newdirs.append(d)
1900 newdirs.append(d)
1870 dirs[:] = newdirs
1901 dirs[:] = newdirs
1871
1902
1872 _rcpath = None
1903 _rcpath = None
1873
1904
1874 def os_rcpath():
1905 def os_rcpath():
1875 '''return default os-specific hgrc search path'''
1906 '''return default os-specific hgrc search path'''
1876 path = system_rcpath()
1907 path = system_rcpath()
1877 path.extend(user_rcpath())
1908 path.extend(user_rcpath())
1878 path = [os.path.normpath(f) for f in path]
1909 path = [os.path.normpath(f) for f in path]
1879 return path
1910 return path
1880
1911
1881 def rcpath():
1912 def rcpath():
1882 '''return hgrc search path. if env var HGRCPATH is set, use it.
1913 '''return hgrc search path. if env var HGRCPATH is set, use it.
1883 for each item in path, if directory, use files ending in .rc,
1914 for each item in path, if directory, use files ending in .rc,
1884 else use item.
1915 else use item.
1885 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1916 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1886 if no HGRCPATH, use default os-specific path.'''
1917 if no HGRCPATH, use default os-specific path.'''
1887 global _rcpath
1918 global _rcpath
1888 if _rcpath is None:
1919 if _rcpath is None:
1889 if 'HGRCPATH' in os.environ:
1920 if 'HGRCPATH' in os.environ:
1890 _rcpath = []
1921 _rcpath = []
1891 for p in os.environ['HGRCPATH'].split(os.pathsep):
1922 for p in os.environ['HGRCPATH'].split(os.pathsep):
1892 if not p: continue
1923 if not p: continue
1893 if os.path.isdir(p):
1924 if os.path.isdir(p):
1894 for f, kind in osutil.listdir(p):
1925 for f, kind in osutil.listdir(p):
1895 if f.endswith('.rc'):
1926 if f.endswith('.rc'):
1896 _rcpath.append(os.path.join(p, f))
1927 _rcpath.append(os.path.join(p, f))
1897 else:
1928 else:
1898 _rcpath.append(p)
1929 _rcpath.append(p)
1899 else:
1930 else:
1900 _rcpath = os_rcpath()
1931 _rcpath = os_rcpath()
1901 return _rcpath
1932 return _rcpath
1902
1933
1903 def bytecount(nbytes):
1934 def bytecount(nbytes):
1904 '''return byte count formatted as readable string, with units'''
1935 '''return byte count formatted as readable string, with units'''
1905
1936
1906 units = (
1937 units = (
1907 (100, 1<<30, _('%.0f GB')),
1938 (100, 1<<30, _('%.0f GB')),
1908 (10, 1<<30, _('%.1f GB')),
1939 (10, 1<<30, _('%.1f GB')),
1909 (1, 1<<30, _('%.2f GB')),
1940 (1, 1<<30, _('%.2f GB')),
1910 (100, 1<<20, _('%.0f MB')),
1941 (100, 1<<20, _('%.0f MB')),
1911 (10, 1<<20, _('%.1f MB')),
1942 (10, 1<<20, _('%.1f MB')),
1912 (1, 1<<20, _('%.2f MB')),
1943 (1, 1<<20, _('%.2f MB')),
1913 (100, 1<<10, _('%.0f KB')),
1944 (100, 1<<10, _('%.0f KB')),
1914 (10, 1<<10, _('%.1f KB')),
1945 (10, 1<<10, _('%.1f KB')),
1915 (1, 1<<10, _('%.2f KB')),
1946 (1, 1<<10, _('%.2f KB')),
1916 (1, 1, _('%.0f bytes')),
1947 (1, 1, _('%.0f bytes')),
1917 )
1948 )
1918
1949
1919 for multiplier, divisor, format in units:
1950 for multiplier, divisor, format in units:
1920 if nbytes >= divisor * multiplier:
1951 if nbytes >= divisor * multiplier:
1921 return format % (nbytes / float(divisor))
1952 return format % (nbytes / float(divisor))
1922 return units[-1][2] % nbytes
1953 return units[-1][2] % nbytes
1923
1954
1924 def drop_scheme(scheme, path):
1955 def drop_scheme(scheme, path):
1925 sc = scheme + ':'
1956 sc = scheme + ':'
1926 if path.startswith(sc):
1957 if path.startswith(sc):
1927 path = path[len(sc):]
1958 path = path[len(sc):]
1928 if path.startswith('//'):
1959 if path.startswith('//'):
1929 path = path[2:]
1960 path = path[2:]
1930 return path
1961 return path
1931
1962
1932 def uirepr(s):
1963 def uirepr(s):
1933 # Avoid double backslash in Windows path repr()
1964 # Avoid double backslash in Windows path repr()
1934 return repr(s).replace('\\\\', '\\')
1965 return repr(s).replace('\\\\', '\\')
General Comments 0
You need to be logged in to leave comments. Login now