##// END OF EJS Templates
Use a case-sensitive version of SafeConfigParser everywhere...
Alexis S. L. Carvalho -
r3425:ec6f400c default
parent child Browse files
Show More
@@ -0,0 +1,7 b''
1 #!/bin/sh
2
3 echo '[Section]' >> $HGRCPATH
4 echo 'KeY = Case Sensitive' >> $HGRCPATH
5 echo 'key = lower case' >> $HGRCPATH
6
7 hg showconfig
@@ -0,0 +1,2 b''
1 Section.KeY=Case Sensitive
2 Section.key=lower case
@@ -1,205 +1,205 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.demandload import demandload
10 from mercurial.demandload import demandload
11 demandload(globals(), "ConfigParser mimetools cStringIO")
11 demandload(globals(), "mimetools cStringIO")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
12 demandload(globals(), "mercurial:ui,hg,util,templater")
13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
13 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
14 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
15 from mercurial.i18n import gettext as _
15 from mercurial.i18n import gettext as _
16
16
17 # This is a stopgap
17 # This is a stopgap
18 class hgwebdir(object):
18 class hgwebdir(object):
19 def __init__(self, config):
19 def __init__(self, config):
20 def cleannames(items):
20 def cleannames(items):
21 return [(name.strip(os.sep), path) for name, path in items]
21 return [(name.strip(os.sep), path) for name, path in items]
22
22
23 self.motd = ""
23 self.motd = ""
24 self.style = ""
24 self.style = ""
25 self.repos_sorted = ('name', False)
25 self.repos_sorted = ('name', False)
26 if isinstance(config, (list, tuple)):
26 if isinstance(config, (list, tuple)):
27 self.repos = cleannames(config)
27 self.repos = cleannames(config)
28 self.repos_sorted = ('', False)
28 self.repos_sorted = ('', False)
29 elif isinstance(config, dict):
29 elif isinstance(config, dict):
30 self.repos = cleannames(config.items())
30 self.repos = cleannames(config.items())
31 self.repos.sort()
31 self.repos.sort()
32 else:
32 else:
33 cp = ConfigParser.SafeConfigParser()
33 cp = util.configparser()
34 cp.read(config)
34 cp.read(config)
35 self.repos = []
35 self.repos = []
36 if cp.has_section('web'):
36 if cp.has_section('web'):
37 if cp.has_option('web', 'motd'):
37 if cp.has_option('web', 'motd'):
38 self.motd = cp.get('web', 'motd')
38 self.motd = cp.get('web', 'motd')
39 if cp.has_option('web', 'style'):
39 if cp.has_option('web', 'style'):
40 self.style = cp.get('web', 'style')
40 self.style = cp.get('web', 'style')
41 if cp.has_section('paths'):
41 if cp.has_section('paths'):
42 self.repos.extend(cleannames(cp.items('paths')))
42 self.repos.extend(cleannames(cp.items('paths')))
43 if cp.has_section('collections'):
43 if cp.has_section('collections'):
44 for prefix, root in cp.items('collections'):
44 for prefix, root in cp.items('collections'):
45 for path in util.walkrepos(root):
45 for path in util.walkrepos(root):
46 repo = os.path.normpath(path)
46 repo = os.path.normpath(path)
47 name = repo
47 name = repo
48 if name.startswith(prefix):
48 if name.startswith(prefix):
49 name = name[len(prefix):]
49 name = name[len(prefix):]
50 self.repos.append((name.lstrip(os.sep), repo))
50 self.repos.append((name.lstrip(os.sep), repo))
51 self.repos.sort()
51 self.repos.sort()
52
52
53 def run(self):
53 def run(self):
54 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
54 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
55 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
55 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
56 import mercurial.hgweb.wsgicgi as wsgicgi
56 import mercurial.hgweb.wsgicgi as wsgicgi
57 from request import wsgiapplication
57 from request import wsgiapplication
58 def make_web_app():
58 def make_web_app():
59 return self
59 return self
60 wsgicgi.launch(wsgiapplication(make_web_app))
60 wsgicgi.launch(wsgiapplication(make_web_app))
61
61
62 def run_wsgi(self, req):
62 def run_wsgi(self, req):
63 def header(**map):
63 def header(**map):
64 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
64 header_file = cStringIO.StringIO(''.join(tmpl("header", **map)))
65 msg = mimetools.Message(header_file, 0)
65 msg = mimetools.Message(header_file, 0)
66 req.header(msg.items())
66 req.header(msg.items())
67 yield header_file.read()
67 yield header_file.read()
68
68
69 def footer(**map):
69 def footer(**map):
70 yield tmpl("footer", motd=self.motd, **map)
70 yield tmpl("footer", motd=self.motd, **map)
71
71
72 url = req.env['REQUEST_URI'].split('?')[0]
72 url = req.env['REQUEST_URI'].split('?')[0]
73 if not url.endswith('/'):
73 if not url.endswith('/'):
74 url += '/'
74 url += '/'
75
75
76 style = self.style
76 style = self.style
77 if req.form.has_key('style'):
77 if req.form.has_key('style'):
78 style = req.form['style'][0]
78 style = req.form['style'][0]
79 mapfile = style_map(templater.templatepath(), style)
79 mapfile = style_map(templater.templatepath(), style)
80 tmpl = templater.templater(mapfile, templater.common_filters,
80 tmpl = templater.templater(mapfile, templater.common_filters,
81 defaults={"header": header,
81 defaults={"header": header,
82 "footer": footer,
82 "footer": footer,
83 "url": url})
83 "url": url})
84
84
85 def archivelist(ui, nodeid, url):
85 def archivelist(ui, nodeid, url):
86 allowed = ui.configlist("web", "allow_archive")
86 allowed = ui.configlist("web", "allow_archive")
87 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
87 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
88 if i[0] in allowed or ui.configbool("web", "allow" + i[0]):
88 if i[0] in allowed or ui.configbool("web", "allow" + i[0]):
89 yield {"type" : i[0], "extension": i[1],
89 yield {"type" : i[0], "extension": i[1],
90 "node": nodeid, "url": url}
90 "node": nodeid, "url": url}
91
91
92 def entries(sortcolumn="", descending=False, **map):
92 def entries(sortcolumn="", descending=False, **map):
93 def sessionvars(**map):
93 def sessionvars(**map):
94 fields = []
94 fields = []
95 if req.form.has_key('style'):
95 if req.form.has_key('style'):
96 style = req.form['style'][0]
96 style = req.form['style'][0]
97 if style != get('web', 'style', ''):
97 if style != get('web', 'style', ''):
98 fields.append(('style', style))
98 fields.append(('style', style))
99
99
100 separator = url[-1] == '?' and ';' or '?'
100 separator = url[-1] == '?' and ';' or '?'
101 for name, value in fields:
101 for name, value in fields:
102 yield dict(name=name, value=value, separator=separator)
102 yield dict(name=name, value=value, separator=separator)
103 separator = ';'
103 separator = ';'
104
104
105 rows = []
105 rows = []
106 parity = 0
106 parity = 0
107 for name, path in self.repos:
107 for name, path in self.repos:
108 u = ui.ui()
108 u = ui.ui()
109 try:
109 try:
110 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
110 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
111 except IOError:
111 except IOError:
112 pass
112 pass
113 get = u.config
113 get = u.config
114
114
115 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
115 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
116 .replace("//", "/")) + '/'
116 .replace("//", "/")) + '/'
117
117
118 # update time with local timezone
118 # update time with local timezone
119 try:
119 try:
120 d = (get_mtime(path), util.makedate()[1])
120 d = (get_mtime(path), util.makedate()[1])
121 except OSError:
121 except OSError:
122 continue
122 continue
123
123
124 contact = (get("ui", "username") or # preferred
124 contact = (get("ui", "username") or # preferred
125 get("web", "contact") or # deprecated
125 get("web", "contact") or # deprecated
126 get("web", "author", "")) # also
126 get("web", "author", "")) # also
127 description = get("web", "description", "")
127 description = get("web", "description", "")
128 name = get("web", "name", name)
128 name = get("web", "name", name)
129 row = dict(contact=contact or "unknown",
129 row = dict(contact=contact or "unknown",
130 contact_sort=contact.upper() or "unknown",
130 contact_sort=contact.upper() or "unknown",
131 name=name,
131 name=name,
132 name_sort=name,
132 name_sort=name,
133 url=url,
133 url=url,
134 description=description or "unknown",
134 description=description or "unknown",
135 description_sort=description.upper() or "unknown",
135 description_sort=description.upper() or "unknown",
136 lastchange=d,
136 lastchange=d,
137 lastchange_sort=d[1]-d[0],
137 lastchange_sort=d[1]-d[0],
138 sessionvars=sessionvars,
138 sessionvars=sessionvars,
139 archives=archivelist(u, "tip", url))
139 archives=archivelist(u, "tip", url))
140 if (not sortcolumn
140 if (not sortcolumn
141 or (sortcolumn, descending) == self.repos_sorted):
141 or (sortcolumn, descending) == self.repos_sorted):
142 # fast path for unsorted output
142 # fast path for unsorted output
143 row['parity'] = parity
143 row['parity'] = parity
144 parity = 1 - parity
144 parity = 1 - parity
145 yield row
145 yield row
146 else:
146 else:
147 rows.append((row["%s_sort" % sortcolumn], row))
147 rows.append((row["%s_sort" % sortcolumn], row))
148 if rows:
148 if rows:
149 rows.sort()
149 rows.sort()
150 if descending:
150 if descending:
151 rows.reverse()
151 rows.reverse()
152 for key, row in rows:
152 for key, row in rows:
153 row['parity'] = parity
153 row['parity'] = parity
154 parity = 1 - parity
154 parity = 1 - parity
155 yield row
155 yield row
156
156
157 virtual = req.env.get("PATH_INFO", "").strip('/')
157 virtual = req.env.get("PATH_INFO", "").strip('/')
158 if virtual.startswith('static/'):
158 if virtual.startswith('static/'):
159 static = os.path.join(templater.templatepath(), 'static')
159 static = os.path.join(templater.templatepath(), 'static')
160 fname = virtual[7:]
160 fname = virtual[7:]
161 req.write(staticfile(static, fname, req) or
161 req.write(staticfile(static, fname, req) or
162 tmpl('error', error='%r not found' % fname))
162 tmpl('error', error='%r not found' % fname))
163 elif virtual:
163 elif virtual:
164 while virtual:
164 while virtual:
165 real = dict(self.repos).get(virtual)
165 real = dict(self.repos).get(virtual)
166 if real:
166 if real:
167 break
167 break
168 up = virtual.rfind('/')
168 up = virtual.rfind('/')
169 if up < 0:
169 if up < 0:
170 break
170 break
171 virtual = virtual[:up]
171 virtual = virtual[:up]
172 if real:
172 if real:
173 req.env['REPO_NAME'] = virtual
173 req.env['REPO_NAME'] = virtual
174 try:
174 try:
175 hgweb(real).run_wsgi(req)
175 hgweb(real).run_wsgi(req)
176 except IOError, inst:
176 except IOError, inst:
177 req.write(tmpl("error", error=inst.strerror))
177 req.write(tmpl("error", error=inst.strerror))
178 except hg.RepoError, inst:
178 except hg.RepoError, inst:
179 req.write(tmpl("error", error=str(inst)))
179 req.write(tmpl("error", error=str(inst)))
180 else:
180 else:
181 req.write(tmpl("notfound", repo=virtual))
181 req.write(tmpl("notfound", repo=virtual))
182 else:
182 else:
183 if req.form.has_key('static'):
183 if req.form.has_key('static'):
184 static = os.path.join(templater.templatepath(), "static")
184 static = os.path.join(templater.templatepath(), "static")
185 fname = req.form['static'][0]
185 fname = req.form['static'][0]
186 req.write(staticfile(static, fname, req)
186 req.write(staticfile(static, fname, req)
187 or tmpl("error", error="%r not found" % fname))
187 or tmpl("error", error="%r not found" % fname))
188 else:
188 else:
189 sortable = ["name", "description", "contact", "lastchange"]
189 sortable = ["name", "description", "contact", "lastchange"]
190 sortcolumn, descending = self.repos_sorted
190 sortcolumn, descending = self.repos_sorted
191 if req.form.has_key('sort'):
191 if req.form.has_key('sort'):
192 sortcolumn = req.form['sort'][0]
192 sortcolumn = req.form['sort'][0]
193 descending = sortcolumn.startswith('-')
193 descending = sortcolumn.startswith('-')
194 if descending:
194 if descending:
195 sortcolumn = sortcolumn[1:]
195 sortcolumn = sortcolumn[1:]
196 if sortcolumn not in sortable:
196 if sortcolumn not in sortable:
197 sortcolumn = ""
197 sortcolumn = ""
198
198
199 sort = [("sort_%s" % column,
199 sort = [("sort_%s" % column,
200 "%s%s" % ((not descending and column == sortcolumn)
200 "%s%s" % ((not descending and column == sortcolumn)
201 and "-" or "", column))
201 and "-" or "", column))
202 for column in sortable]
202 for column in sortable]
203 req.write(tmpl("index", entries=entries,
203 req.write(tmpl("index", entries=entries,
204 sortcolumn=sortcolumn, descending=descending,
204 sortcolumn=sortcolumn, descending=descending,
205 **dict(sort)))
205 **dict(sort)))
@@ -1,323 +1,323 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
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 gettext as _
8 from i18n import gettext as _
9 from demandload import *
9 from demandload import *
10 demandload(globals(), "errno getpass os re socket sys tempfile")
10 demandload(globals(), "errno getpass os re socket sys tempfile")
11 demandload(globals(), "ConfigParser traceback util")
11 demandload(globals(), "ConfigParser traceback util")
12
12
13 def dupconfig(orig):
13 def dupconfig(orig):
14 new = ConfigParser.SafeConfigParser(orig.defaults())
14 new = util.configparser(orig.defaults())
15 updateconfig(orig, new)
15 updateconfig(orig, new)
16 return new
16 return new
17
17
18 def updateconfig(source, dest):
18 def updateconfig(source, dest):
19 for section in source.sections():
19 for section in source.sections():
20 if not dest.has_section(section):
20 if not dest.has_section(section):
21 dest.add_section(section)
21 dest.add_section(section)
22 for name, value in source.items(section, raw=True):
22 for name, value in source.items(section, raw=True):
23 dest.set(section, name, value)
23 dest.set(section, name, value)
24
24
25 class ui(object):
25 class ui(object):
26 def __init__(self, verbose=False, debug=False, quiet=False,
26 def __init__(self, verbose=False, debug=False, quiet=False,
27 interactive=True, traceback=False, parentui=None):
27 interactive=True, traceback=False, parentui=None):
28 self.overlay = None
28 self.overlay = None
29 self.header = []
29 self.header = []
30 self.prev_header = []
30 self.prev_header = []
31 if parentui is None:
31 if parentui is None:
32 # this is the parent of all ui children
32 # this is the parent of all ui children
33 self.parentui = None
33 self.parentui = None
34 self.readhooks = []
34 self.readhooks = []
35 self.quiet = quiet
35 self.quiet = quiet
36 self.verbose = verbose
36 self.verbose = verbose
37 self.debugflag = debug
37 self.debugflag = debug
38 self.interactive = interactive
38 self.interactive = interactive
39 self.traceback = traceback
39 self.traceback = traceback
40 self.cdata = ConfigParser.SafeConfigParser()
40 self.cdata = util.configparser()
41 self.readconfig(util.rcpath())
41 self.readconfig(util.rcpath())
42 self.updateopts(verbose, debug, quiet, interactive)
42 self.updateopts(verbose, debug, quiet, interactive)
43 else:
43 else:
44 # parentui may point to an ui object which is already a child
44 # parentui may point to an ui object which is already a child
45 self.parentui = parentui.parentui or parentui
45 self.parentui = parentui.parentui or parentui
46 self.readhooks = self.parentui.readhooks[:]
46 self.readhooks = self.parentui.readhooks[:]
47 self.cdata = dupconfig(self.parentui.cdata)
47 self.cdata = dupconfig(self.parentui.cdata)
48 if self.parentui.overlay:
48 if self.parentui.overlay:
49 self.overlay = dupconfig(self.parentui.overlay)
49 self.overlay = dupconfig(self.parentui.overlay)
50
50
51 def __getattr__(self, key):
51 def __getattr__(self, key):
52 return getattr(self.parentui, key)
52 return getattr(self.parentui, key)
53
53
54 def updateopts(self, verbose=False, debug=False, quiet=False,
54 def updateopts(self, verbose=False, debug=False, quiet=False,
55 interactive=True, traceback=False, config=[]):
55 interactive=True, traceback=False, config=[]):
56 for section, name, value in config:
56 for section, name, value in config:
57 self.setconfig(section, name, value)
57 self.setconfig(section, name, value)
58
58
59 if quiet or verbose or debug:
59 if quiet or verbose or debug:
60 self.setconfig('ui', 'quiet', str(bool(quiet)))
60 self.setconfig('ui', 'quiet', str(bool(quiet)))
61 self.setconfig('ui', 'verbose', str(bool(verbose)))
61 self.setconfig('ui', 'verbose', str(bool(verbose)))
62 self.setconfig('ui', 'debug', str(bool(debug)))
62 self.setconfig('ui', 'debug', str(bool(debug)))
63
63
64 self.verbosity_constraints()
64 self.verbosity_constraints()
65
65
66 if not interactive:
66 if not interactive:
67 self.setconfig('ui', 'interactive', 'False')
67 self.setconfig('ui', 'interactive', 'False')
68 self.interactive = False
68 self.interactive = False
69
69
70 self.traceback = self.traceback or traceback
70 self.traceback = self.traceback or traceback
71
71
72 def verbosity_constraints(self):
72 def verbosity_constraints(self):
73 self.quiet = self.configbool('ui', 'quiet')
73 self.quiet = self.configbool('ui', 'quiet')
74 self.verbose = self.configbool('ui', 'verbose')
74 self.verbose = self.configbool('ui', 'verbose')
75 self.debugflag = self.configbool('ui', 'debug')
75 self.debugflag = self.configbool('ui', 'debug')
76
76
77 if self.debugflag:
77 if self.debugflag:
78 self.verbose = True
78 self.verbose = True
79 self.quiet = False
79 self.quiet = False
80 elif self.verbose and self.quiet:
80 elif self.verbose and self.quiet:
81 self.quiet = self.verbose = False
81 self.quiet = self.verbose = False
82
82
83 def readconfig(self, fn, root=None):
83 def readconfig(self, fn, root=None):
84 if isinstance(fn, basestring):
84 if isinstance(fn, basestring):
85 fn = [fn]
85 fn = [fn]
86 for f in fn:
86 for f in fn:
87 try:
87 try:
88 self.cdata.read(f)
88 self.cdata.read(f)
89 except ConfigParser.ParsingError, inst:
89 except ConfigParser.ParsingError, inst:
90 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
90 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
91 # override data from config files with data set with ui.setconfig
91 # override data from config files with data set with ui.setconfig
92 if self.overlay:
92 if self.overlay:
93 updateconfig(self.overlay, self.cdata)
93 updateconfig(self.overlay, self.cdata)
94 if root is None:
94 if root is None:
95 root = os.path.expanduser('~')
95 root = os.path.expanduser('~')
96 self.fixconfig(root=root)
96 self.fixconfig(root=root)
97 for hook in self.readhooks:
97 for hook in self.readhooks:
98 hook(self)
98 hook(self)
99
99
100 def addreadhook(self, hook):
100 def addreadhook(self, hook):
101 self.readhooks.append(hook)
101 self.readhooks.append(hook)
102
102
103 def fixconfig(self, section=None, name=None, value=None, root=None):
103 def fixconfig(self, section=None, name=None, value=None, root=None):
104 # translate paths relative to root (or home) into absolute paths
104 # translate paths relative to root (or home) into absolute paths
105 if section is None or section == 'paths':
105 if section is None or section == 'paths':
106 if root is None:
106 if root is None:
107 root = os.getcwd()
107 root = os.getcwd()
108 items = section and [(name, value)] or []
108 items = section and [(name, value)] or []
109 for cdata in self.cdata, self.overlay:
109 for cdata in self.cdata, self.overlay:
110 if not cdata: continue
110 if not cdata: continue
111 if not items and cdata.has_section('paths'):
111 if not items and cdata.has_section('paths'):
112 pathsitems = cdata.items('paths')
112 pathsitems = cdata.items('paths')
113 else:
113 else:
114 pathsitems = items
114 pathsitems = items
115 for n, path in pathsitems:
115 for n, path in pathsitems:
116 if path and "://" not in path and not os.path.isabs(path):
116 if path and "://" not in path and not os.path.isabs(path):
117 cdata.set("paths", n, os.path.join(root, path))
117 cdata.set("paths", n, os.path.join(root, path))
118
118
119 # update quiet/verbose/debug and interactive status
119 # update quiet/verbose/debug and interactive status
120 if section is None or section == 'ui':
120 if section is None or section == 'ui':
121 if name is None or name in ('quiet', 'verbose', 'debug'):
121 if name is None or name in ('quiet', 'verbose', 'debug'):
122 self.verbosity_constraints()
122 self.verbosity_constraints()
123
123
124 if name is None or name == 'interactive':
124 if name is None or name == 'interactive':
125 self.interactive = self.configbool("ui", "interactive", True)
125 self.interactive = self.configbool("ui", "interactive", True)
126
126
127 def setconfig(self, section, name, value):
127 def setconfig(self, section, name, value):
128 if not self.overlay:
128 if not self.overlay:
129 self.overlay = ConfigParser.SafeConfigParser()
129 self.overlay = util.configparser()
130 for cdata in (self.overlay, self.cdata):
130 for cdata in (self.overlay, self.cdata):
131 if not cdata.has_section(section):
131 if not cdata.has_section(section):
132 cdata.add_section(section)
132 cdata.add_section(section)
133 cdata.set(section, name, value)
133 cdata.set(section, name, value)
134 self.fixconfig(section, name, value)
134 self.fixconfig(section, name, value)
135
135
136 def _config(self, section, name, default, funcname):
136 def _config(self, section, name, default, funcname):
137 if self.cdata.has_option(section, name):
137 if self.cdata.has_option(section, name):
138 try:
138 try:
139 func = getattr(self.cdata, funcname)
139 func = getattr(self.cdata, funcname)
140 return func(section, name)
140 return func(section, name)
141 except ConfigParser.InterpolationError, inst:
141 except ConfigParser.InterpolationError, inst:
142 raise util.Abort(_("Error in configuration section [%s] "
142 raise util.Abort(_("Error in configuration section [%s] "
143 "parameter '%s':\n%s")
143 "parameter '%s':\n%s")
144 % (section, name, inst))
144 % (section, name, inst))
145 return default
145 return default
146
146
147 def config(self, section, name, default=None):
147 def config(self, section, name, default=None):
148 return self._config(section, name, default, 'get')
148 return self._config(section, name, default, 'get')
149
149
150 def configbool(self, section, name, default=False):
150 def configbool(self, section, name, default=False):
151 return self._config(section, name, default, 'getboolean')
151 return self._config(section, name, default, 'getboolean')
152
152
153 def configlist(self, section, name, default=None):
153 def configlist(self, section, name, default=None):
154 """Return a list of comma/space separated strings"""
154 """Return a list of comma/space separated strings"""
155 result = self.config(section, name)
155 result = self.config(section, name)
156 if result is None:
156 if result is None:
157 result = default or []
157 result = default or []
158 if isinstance(result, basestring):
158 if isinstance(result, basestring):
159 result = result.replace(",", " ").split()
159 result = result.replace(",", " ").split()
160 return result
160 return result
161
161
162 def has_config(self, section):
162 def has_config(self, section):
163 '''tell whether section exists in config.'''
163 '''tell whether section exists in config.'''
164 return self.cdata.has_section(section)
164 return self.cdata.has_section(section)
165
165
166 def configitems(self, section):
166 def configitems(self, section):
167 items = {}
167 items = {}
168 if self.cdata.has_section(section):
168 if self.cdata.has_section(section):
169 try:
169 try:
170 items.update(dict(self.cdata.items(section)))
170 items.update(dict(self.cdata.items(section)))
171 except ConfigParser.InterpolationError, inst:
171 except ConfigParser.InterpolationError, inst:
172 raise util.Abort(_("Error in configuration section [%s]:\n%s")
172 raise util.Abort(_("Error in configuration section [%s]:\n%s")
173 % (section, inst))
173 % (section, inst))
174 x = items.items()
174 x = items.items()
175 x.sort()
175 x.sort()
176 return x
176 return x
177
177
178 def walkconfig(self):
178 def walkconfig(self):
179 sections = self.cdata.sections()
179 sections = self.cdata.sections()
180 sections.sort()
180 sections.sort()
181 for section in sections:
181 for section in sections:
182 for name, value in self.configitems(section):
182 for name, value in self.configitems(section):
183 yield section, name, value.replace('\n', '\\n')
183 yield section, name, value.replace('\n', '\\n')
184
184
185 def extensions(self):
185 def extensions(self):
186 result = self.configitems("extensions")
186 result = self.configitems("extensions")
187 for i, (key, value) in enumerate(result):
187 for i, (key, value) in enumerate(result):
188 if value:
188 if value:
189 result[i] = (key, os.path.expanduser(value))
189 result[i] = (key, os.path.expanduser(value))
190 return result
190 return result
191
191
192 def hgignorefiles(self):
192 def hgignorefiles(self):
193 result = []
193 result = []
194 for key, value in self.configitems("ui"):
194 for key, value in self.configitems("ui"):
195 if key == 'ignore' or key.startswith('ignore.'):
195 if key == 'ignore' or key.startswith('ignore.'):
196 result.append(os.path.expanduser(value))
196 result.append(os.path.expanduser(value))
197 return result
197 return result
198
198
199 def configrevlog(self):
199 def configrevlog(self):
200 result = {}
200 result = {}
201 for key, value in self.configitems("revlog"):
201 for key, value in self.configitems("revlog"):
202 result[key.lower()] = value
202 result[key.lower()] = value
203 return result
203 return result
204
204
205 def username(self):
205 def username(self):
206 """Return default username to be used in commits.
206 """Return default username to be used in commits.
207
207
208 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
208 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
209 and stop searching if one of these is set.
209 and stop searching if one of these is set.
210 Abort if found username is an empty string to force specifying
210 Abort if found username is an empty string to force specifying
211 the commit user elsewhere, e.g. with line option or repo hgrc.
211 the commit user elsewhere, e.g. with line option or repo hgrc.
212 If not found, use ($LOGNAME or $USER or $LNAME or
212 If not found, use ($LOGNAME or $USER or $LNAME or
213 $USERNAME) +"@full.hostname".
213 $USERNAME) +"@full.hostname".
214 """
214 """
215 user = os.environ.get("HGUSER")
215 user = os.environ.get("HGUSER")
216 if user is None:
216 if user is None:
217 user = self.config("ui", "username")
217 user = self.config("ui", "username")
218 if user is None:
218 if user is None:
219 user = os.environ.get("EMAIL")
219 user = os.environ.get("EMAIL")
220 if user is None:
220 if user is None:
221 try:
221 try:
222 user = '%s@%s' % (util.getuser(), socket.getfqdn())
222 user = '%s@%s' % (util.getuser(), socket.getfqdn())
223 except KeyError:
223 except KeyError:
224 raise util.Abort(_("Please specify a username."))
224 raise util.Abort(_("Please specify a username."))
225 return user
225 return user
226
226
227 def shortuser(self, user):
227 def shortuser(self, user):
228 """Return a short representation of a user name or email address."""
228 """Return a short representation of a user name or email address."""
229 if not self.verbose: user = util.shortuser(user)
229 if not self.verbose: user = util.shortuser(user)
230 return user
230 return user
231
231
232 def expandpath(self, loc, default=None):
232 def expandpath(self, loc, default=None):
233 """Return repository location relative to cwd or from [paths]"""
233 """Return repository location relative to cwd or from [paths]"""
234 if "://" in loc or os.path.isdir(loc):
234 if "://" in loc or os.path.isdir(loc):
235 return loc
235 return loc
236
236
237 path = self.config("paths", loc)
237 path = self.config("paths", loc)
238 if not path and default is not None:
238 if not path and default is not None:
239 path = self.config("paths", default)
239 path = self.config("paths", default)
240 return path or loc
240 return path or loc
241
241
242 def write(self, *args):
242 def write(self, *args):
243 if self.header:
243 if self.header:
244 if self.header != self.prev_header:
244 if self.header != self.prev_header:
245 self.prev_header = self.header
245 self.prev_header = self.header
246 self.write(*self.header)
246 self.write(*self.header)
247 self.header = []
247 self.header = []
248 for a in args:
248 for a in args:
249 sys.stdout.write(str(a))
249 sys.stdout.write(str(a))
250
250
251 def write_header(self, *args):
251 def write_header(self, *args):
252 for a in args:
252 for a in args:
253 self.header.append(str(a))
253 self.header.append(str(a))
254
254
255 def write_err(self, *args):
255 def write_err(self, *args):
256 try:
256 try:
257 if not sys.stdout.closed: sys.stdout.flush()
257 if not sys.stdout.closed: sys.stdout.flush()
258 for a in args:
258 for a in args:
259 sys.stderr.write(str(a))
259 sys.stderr.write(str(a))
260 except IOError, inst:
260 except IOError, inst:
261 if inst.errno != errno.EPIPE:
261 if inst.errno != errno.EPIPE:
262 raise
262 raise
263
263
264 def flush(self):
264 def flush(self):
265 try: sys.stdout.flush()
265 try: sys.stdout.flush()
266 except: pass
266 except: pass
267 try: sys.stderr.flush()
267 try: sys.stderr.flush()
268 except: pass
268 except: pass
269
269
270 def readline(self):
270 def readline(self):
271 return sys.stdin.readline()[:-1]
271 return sys.stdin.readline()[:-1]
272 def prompt(self, msg, pat=None, default="y"):
272 def prompt(self, msg, pat=None, default="y"):
273 if not self.interactive: return default
273 if not self.interactive: return default
274 while 1:
274 while 1:
275 self.write(msg, " ")
275 self.write(msg, " ")
276 r = self.readline()
276 r = self.readline()
277 if not pat or re.match(pat, r):
277 if not pat or re.match(pat, r):
278 return r
278 return r
279 else:
279 else:
280 self.write(_("unrecognized response\n"))
280 self.write(_("unrecognized response\n"))
281 def getpass(self, prompt=None, default=None):
281 def getpass(self, prompt=None, default=None):
282 if not self.interactive: return default
282 if not self.interactive: return default
283 return getpass.getpass(prompt or _('password: '))
283 return getpass.getpass(prompt or _('password: '))
284 def status(self, *msg):
284 def status(self, *msg):
285 if not self.quiet: self.write(*msg)
285 if not self.quiet: self.write(*msg)
286 def warn(self, *msg):
286 def warn(self, *msg):
287 self.write_err(*msg)
287 self.write_err(*msg)
288 def note(self, *msg):
288 def note(self, *msg):
289 if self.verbose: self.write(*msg)
289 if self.verbose: self.write(*msg)
290 def debug(self, *msg):
290 def debug(self, *msg):
291 if self.debugflag: self.write(*msg)
291 if self.debugflag: self.write(*msg)
292 def edit(self, text, user):
292 def edit(self, text, user):
293 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
293 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
294 text=True)
294 text=True)
295 try:
295 try:
296 f = os.fdopen(fd, "w")
296 f = os.fdopen(fd, "w")
297 f.write(text)
297 f.write(text)
298 f.close()
298 f.close()
299
299
300 editor = (os.environ.get("HGEDITOR") or
300 editor = (os.environ.get("HGEDITOR") or
301 self.config("ui", "editor") or
301 self.config("ui", "editor") or
302 os.environ.get("EDITOR", "vi"))
302 os.environ.get("EDITOR", "vi"))
303
303
304 util.system("%s \"%s\"" % (editor, name),
304 util.system("%s \"%s\"" % (editor, name),
305 environ={'HGUSER': user},
305 environ={'HGUSER': user},
306 onerr=util.Abort, errprefix=_("edit failed"))
306 onerr=util.Abort, errprefix=_("edit failed"))
307
307
308 f = open(name)
308 f = open(name)
309 t = f.read()
309 t = f.read()
310 f.close()
310 f.close()
311 t = re.sub("(?m)^HG:.*\n", "", t)
311 t = re.sub("(?m)^HG:.*\n", "", t)
312 finally:
312 finally:
313 os.unlink(name)
313 os.unlink(name)
314
314
315 return t
315 return t
316
316
317 def print_exc(self):
317 def print_exc(self):
318 '''print exception traceback if traceback printing enabled.
318 '''print exception traceback if traceback printing enabled.
319 only to call in exception handler. returns true if traceback
319 only to call in exception handler. returns true if traceback
320 printed.'''
320 printed.'''
321 if self.traceback:
321 if self.traceback:
322 traceback.print_exc()
322 traceback.print_exc()
323 return self.traceback
323 return self.traceback
@@ -1,1030 +1,1035 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, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import gettext as _
15 from i18n import gettext as _
16 from demandload import *
16 from demandload import *
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 demandload(globals(), "os threading time calendar")
18 demandload(globals(), "os threading time calendar ConfigParser")
19
19
20 # used by parsedate
20 # used by parsedate
21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
22 '%a %b %d %H:%M:%S %Y')
22 '%a %b %d %H:%M:%S %Y')
23
23
24 class SignalInterrupt(Exception):
24 class SignalInterrupt(Exception):
25 """Exception raised on SIGTERM and SIGHUP."""
25 """Exception raised on SIGTERM and SIGHUP."""
26
26
27 # like SafeConfigParser but with case-sensitive keys
28 class configparser(ConfigParser.SafeConfigParser):
29 def optionxform(self, optionstr):
30 return optionstr
31
27 def cachefunc(func):
32 def cachefunc(func):
28 '''cache the result of function calls'''
33 '''cache the result of function calls'''
29 # XXX doesn't handle keywords args
34 # XXX doesn't handle keywords args
30 cache = {}
35 cache = {}
31 if func.func_code.co_argcount == 1:
36 if func.func_code.co_argcount == 1:
32 # we gain a small amount of time because
37 # we gain a small amount of time because
33 # we don't need to pack/unpack the list
38 # we don't need to pack/unpack the list
34 def f(arg):
39 def f(arg):
35 if arg not in cache:
40 if arg not in cache:
36 cache[arg] = func(arg)
41 cache[arg] = func(arg)
37 return cache[arg]
42 return cache[arg]
38 else:
43 else:
39 def f(*args):
44 def f(*args):
40 if args not in cache:
45 if args not in cache:
41 cache[args] = func(*args)
46 cache[args] = func(*args)
42 return cache[args]
47 return cache[args]
43
48
44 return f
49 return f
45
50
46 def pipefilter(s, cmd):
51 def pipefilter(s, cmd):
47 '''filter string S through command CMD, returning its output'''
52 '''filter string S through command CMD, returning its output'''
48 (pout, pin) = popen2.popen2(cmd, -1, 'b')
53 (pout, pin) = popen2.popen2(cmd, -1, 'b')
49 def writer():
54 def writer():
50 try:
55 try:
51 pin.write(s)
56 pin.write(s)
52 pin.close()
57 pin.close()
53 except IOError, inst:
58 except IOError, inst:
54 if inst.errno != errno.EPIPE:
59 if inst.errno != errno.EPIPE:
55 raise
60 raise
56
61
57 # we should use select instead on UNIX, but this will work on most
62 # we should use select instead on UNIX, but this will work on most
58 # systems, including Windows
63 # systems, including Windows
59 w = threading.Thread(target=writer)
64 w = threading.Thread(target=writer)
60 w.start()
65 w.start()
61 f = pout.read()
66 f = pout.read()
62 pout.close()
67 pout.close()
63 w.join()
68 w.join()
64 return f
69 return f
65
70
66 def tempfilter(s, cmd):
71 def tempfilter(s, cmd):
67 '''filter string S through a pair of temporary files with CMD.
72 '''filter string S through a pair of temporary files with CMD.
68 CMD is used as a template to create the real command to be run,
73 CMD is used as a template to create the real command to be run,
69 with the strings INFILE and OUTFILE replaced by the real names of
74 with the strings INFILE and OUTFILE replaced by the real names of
70 the temporary files generated.'''
75 the temporary files generated.'''
71 inname, outname = None, None
76 inname, outname = None, None
72 try:
77 try:
73 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
78 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
74 fp = os.fdopen(infd, 'wb')
79 fp = os.fdopen(infd, 'wb')
75 fp.write(s)
80 fp.write(s)
76 fp.close()
81 fp.close()
77 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
82 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
78 os.close(outfd)
83 os.close(outfd)
79 cmd = cmd.replace('INFILE', inname)
84 cmd = cmd.replace('INFILE', inname)
80 cmd = cmd.replace('OUTFILE', outname)
85 cmd = cmd.replace('OUTFILE', outname)
81 code = os.system(cmd)
86 code = os.system(cmd)
82 if code: raise Abort(_("command '%s' failed: %s") %
87 if code: raise Abort(_("command '%s' failed: %s") %
83 (cmd, explain_exit(code)))
88 (cmd, explain_exit(code)))
84 return open(outname, 'rb').read()
89 return open(outname, 'rb').read()
85 finally:
90 finally:
86 try:
91 try:
87 if inname: os.unlink(inname)
92 if inname: os.unlink(inname)
88 except: pass
93 except: pass
89 try:
94 try:
90 if outname: os.unlink(outname)
95 if outname: os.unlink(outname)
91 except: pass
96 except: pass
92
97
93 filtertable = {
98 filtertable = {
94 'tempfile:': tempfilter,
99 'tempfile:': tempfilter,
95 'pipe:': pipefilter,
100 'pipe:': pipefilter,
96 }
101 }
97
102
98 def filter(s, cmd):
103 def filter(s, cmd):
99 "filter a string through a command that transforms its input to its output"
104 "filter a string through a command that transforms its input to its output"
100 for name, fn in filtertable.iteritems():
105 for name, fn in filtertable.iteritems():
101 if cmd.startswith(name):
106 if cmd.startswith(name):
102 return fn(s, cmd[len(name):].lstrip())
107 return fn(s, cmd[len(name):].lstrip())
103 return pipefilter(s, cmd)
108 return pipefilter(s, cmd)
104
109
105 def find_in_path(name, path, default=None):
110 def find_in_path(name, path, default=None):
106 '''find name in search path. path can be string (will be split
111 '''find name in search path. path can be string (will be split
107 with os.pathsep), or iterable thing that returns strings. if name
112 with os.pathsep), or iterable thing that returns strings. if name
108 found, return path to name. else return default.'''
113 found, return path to name. else return default.'''
109 if isinstance(path, str):
114 if isinstance(path, str):
110 path = path.split(os.pathsep)
115 path = path.split(os.pathsep)
111 for p in path:
116 for p in path:
112 p_name = os.path.join(p, name)
117 p_name = os.path.join(p, name)
113 if os.path.exists(p_name):
118 if os.path.exists(p_name):
114 return p_name
119 return p_name
115 return default
120 return default
116
121
117 def binary(s):
122 def binary(s):
118 """return true if a string is binary data using diff's heuristic"""
123 """return true if a string is binary data using diff's heuristic"""
119 if s and '\0' in s[:4096]:
124 if s and '\0' in s[:4096]:
120 return True
125 return True
121 return False
126 return False
122
127
123 def unique(g):
128 def unique(g):
124 """return the uniq elements of iterable g"""
129 """return the uniq elements of iterable g"""
125 seen = {}
130 seen = {}
126 for f in g:
131 for f in g:
127 if f not in seen:
132 if f not in seen:
128 seen[f] = 1
133 seen[f] = 1
129 yield f
134 yield f
130
135
131 class Abort(Exception):
136 class Abort(Exception):
132 """Raised if a command needs to print an error and exit."""
137 """Raised if a command needs to print an error and exit."""
133
138
134 def always(fn): return True
139 def always(fn): return True
135 def never(fn): return False
140 def never(fn): return False
136
141
137 def patkind(name, dflt_pat='glob'):
142 def patkind(name, dflt_pat='glob'):
138 """Split a string into an optional pattern kind prefix and the
143 """Split a string into an optional pattern kind prefix and the
139 actual pattern."""
144 actual pattern."""
140 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
145 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
141 if name.startswith(prefix + ':'): return name.split(':', 1)
146 if name.startswith(prefix + ':'): return name.split(':', 1)
142 return dflt_pat, name
147 return dflt_pat, name
143
148
144 def globre(pat, head='^', tail='$'):
149 def globre(pat, head='^', tail='$'):
145 "convert a glob pattern into a regexp"
150 "convert a glob pattern into a regexp"
146 i, n = 0, len(pat)
151 i, n = 0, len(pat)
147 res = ''
152 res = ''
148 group = False
153 group = False
149 def peek(): return i < n and pat[i]
154 def peek(): return i < n and pat[i]
150 while i < n:
155 while i < n:
151 c = pat[i]
156 c = pat[i]
152 i = i+1
157 i = i+1
153 if c == '*':
158 if c == '*':
154 if peek() == '*':
159 if peek() == '*':
155 i += 1
160 i += 1
156 res += '.*'
161 res += '.*'
157 else:
162 else:
158 res += '[^/]*'
163 res += '[^/]*'
159 elif c == '?':
164 elif c == '?':
160 res += '.'
165 res += '.'
161 elif c == '[':
166 elif c == '[':
162 j = i
167 j = i
163 if j < n and pat[j] in '!]':
168 if j < n and pat[j] in '!]':
164 j += 1
169 j += 1
165 while j < n and pat[j] != ']':
170 while j < n and pat[j] != ']':
166 j += 1
171 j += 1
167 if j >= n:
172 if j >= n:
168 res += '\\['
173 res += '\\['
169 else:
174 else:
170 stuff = pat[i:j].replace('\\','\\\\')
175 stuff = pat[i:j].replace('\\','\\\\')
171 i = j + 1
176 i = j + 1
172 if stuff[0] == '!':
177 if stuff[0] == '!':
173 stuff = '^' + stuff[1:]
178 stuff = '^' + stuff[1:]
174 elif stuff[0] == '^':
179 elif stuff[0] == '^':
175 stuff = '\\' + stuff
180 stuff = '\\' + stuff
176 res = '%s[%s]' % (res, stuff)
181 res = '%s[%s]' % (res, stuff)
177 elif c == '{':
182 elif c == '{':
178 group = True
183 group = True
179 res += '(?:'
184 res += '(?:'
180 elif c == '}' and group:
185 elif c == '}' and group:
181 res += ')'
186 res += ')'
182 group = False
187 group = False
183 elif c == ',' and group:
188 elif c == ',' and group:
184 res += '|'
189 res += '|'
185 elif c == '\\':
190 elif c == '\\':
186 p = peek()
191 p = peek()
187 if p:
192 if p:
188 i += 1
193 i += 1
189 res += re.escape(p)
194 res += re.escape(p)
190 else:
195 else:
191 res += re.escape(c)
196 res += re.escape(c)
192 else:
197 else:
193 res += re.escape(c)
198 res += re.escape(c)
194 return head + res + tail
199 return head + res + tail
195
200
196 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
201 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
197
202
198 def pathto(n1, n2):
203 def pathto(n1, n2):
199 '''return the relative path from one place to another.
204 '''return the relative path from one place to another.
200 this returns a path in the form used by the local filesystem, not hg.'''
205 this returns a path in the form used by the local filesystem, not hg.'''
201 if not n1: return localpath(n2)
206 if not n1: return localpath(n2)
202 a, b = n1.split('/'), n2.split('/')
207 a, b = n1.split('/'), n2.split('/')
203 a.reverse()
208 a.reverse()
204 b.reverse()
209 b.reverse()
205 while a and b and a[-1] == b[-1]:
210 while a and b and a[-1] == b[-1]:
206 a.pop()
211 a.pop()
207 b.pop()
212 b.pop()
208 b.reverse()
213 b.reverse()
209 return os.sep.join((['..'] * len(a)) + b)
214 return os.sep.join((['..'] * len(a)) + b)
210
215
211 def canonpath(root, cwd, myname):
216 def canonpath(root, cwd, myname):
212 """return the canonical path of myname, given cwd and root"""
217 """return the canonical path of myname, given cwd and root"""
213 if root == os.sep:
218 if root == os.sep:
214 rootsep = os.sep
219 rootsep = os.sep
215 elif root.endswith(os.sep):
220 elif root.endswith(os.sep):
216 rootsep = root
221 rootsep = root
217 else:
222 else:
218 rootsep = root + os.sep
223 rootsep = root + os.sep
219 name = myname
224 name = myname
220 if not os.path.isabs(name):
225 if not os.path.isabs(name):
221 name = os.path.join(root, cwd, name)
226 name = os.path.join(root, cwd, name)
222 name = os.path.normpath(name)
227 name = os.path.normpath(name)
223 if name != rootsep and name.startswith(rootsep):
228 if name != rootsep and name.startswith(rootsep):
224 name = name[len(rootsep):]
229 name = name[len(rootsep):]
225 audit_path(name)
230 audit_path(name)
226 return pconvert(name)
231 return pconvert(name)
227 elif name == root:
232 elif name == root:
228 return ''
233 return ''
229 else:
234 else:
230 # Determine whether `name' is in the hierarchy at or beneath `root',
235 # Determine whether `name' is in the hierarchy at or beneath `root',
231 # by iterating name=dirname(name) until that causes no change (can't
236 # by iterating name=dirname(name) until that causes no change (can't
232 # check name == '/', because that doesn't work on windows). For each
237 # check name == '/', because that doesn't work on windows). For each
233 # `name', compare dev/inode numbers. If they match, the list `rel'
238 # `name', compare dev/inode numbers. If they match, the list `rel'
234 # holds the reversed list of components making up the relative file
239 # holds the reversed list of components making up the relative file
235 # name we want.
240 # name we want.
236 root_st = os.stat(root)
241 root_st = os.stat(root)
237 rel = []
242 rel = []
238 while True:
243 while True:
239 try:
244 try:
240 name_st = os.stat(name)
245 name_st = os.stat(name)
241 except OSError:
246 except OSError:
242 break
247 break
243 if samestat(name_st, root_st):
248 if samestat(name_st, root_st):
244 rel.reverse()
249 rel.reverse()
245 name = os.path.join(*rel)
250 name = os.path.join(*rel)
246 audit_path(name)
251 audit_path(name)
247 return pconvert(name)
252 return pconvert(name)
248 dirname, basename = os.path.split(name)
253 dirname, basename = os.path.split(name)
249 rel.append(basename)
254 rel.append(basename)
250 if dirname == name:
255 if dirname == name:
251 break
256 break
252 name = dirname
257 name = dirname
253
258
254 raise Abort('%s not under root' % myname)
259 raise Abort('%s not under root' % myname)
255
260
256 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
261 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
257 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
262 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
258
263
259 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
264 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
260 if os.name == 'nt':
265 if os.name == 'nt':
261 dflt_pat = 'glob'
266 dflt_pat = 'glob'
262 else:
267 else:
263 dflt_pat = 'relpath'
268 dflt_pat = 'relpath'
264 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
269 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
265
270
266 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
271 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
267 """build a function to match a set of file patterns
272 """build a function to match a set of file patterns
268
273
269 arguments:
274 arguments:
270 canonroot - the canonical root of the tree you're matching against
275 canonroot - the canonical root of the tree you're matching against
271 cwd - the current working directory, if relevant
276 cwd - the current working directory, if relevant
272 names - patterns to find
277 names - patterns to find
273 inc - patterns to include
278 inc - patterns to include
274 exc - patterns to exclude
279 exc - patterns to exclude
275 head - a regex to prepend to patterns to control whether a match is rooted
280 head - a regex to prepend to patterns to control whether a match is rooted
276
281
277 a pattern is one of:
282 a pattern is one of:
278 'glob:<rooted glob>'
283 'glob:<rooted glob>'
279 're:<rooted regexp>'
284 're:<rooted regexp>'
280 'path:<rooted path>'
285 'path:<rooted path>'
281 'relglob:<relative glob>'
286 'relglob:<relative glob>'
282 'relpath:<relative path>'
287 'relpath:<relative path>'
283 'relre:<relative regexp>'
288 'relre:<relative regexp>'
284 '<rooted path or regexp>'
289 '<rooted path or regexp>'
285
290
286 returns:
291 returns:
287 a 3-tuple containing
292 a 3-tuple containing
288 - list of explicit non-pattern names passed in
293 - list of explicit non-pattern names passed in
289 - a bool match(filename) function
294 - a bool match(filename) function
290 - a bool indicating if any patterns were passed in
295 - a bool indicating if any patterns were passed in
291
296
292 todo:
297 todo:
293 make head regex a rooted bool
298 make head regex a rooted bool
294 """
299 """
295
300
296 def contains_glob(name):
301 def contains_glob(name):
297 for c in name:
302 for c in name:
298 if c in _globchars: return True
303 if c in _globchars: return True
299 return False
304 return False
300
305
301 def regex(kind, name, tail):
306 def regex(kind, name, tail):
302 '''convert a pattern into a regular expression'''
307 '''convert a pattern into a regular expression'''
303 if kind == 're':
308 if kind == 're':
304 return name
309 return name
305 elif kind == 'path':
310 elif kind == 'path':
306 return '^' + re.escape(name) + '(?:/|$)'
311 return '^' + re.escape(name) + '(?:/|$)'
307 elif kind == 'relglob':
312 elif kind == 'relglob':
308 return head + globre(name, '(?:|.*/)', tail)
313 return head + globre(name, '(?:|.*/)', tail)
309 elif kind == 'relpath':
314 elif kind == 'relpath':
310 return head + re.escape(name) + tail
315 return head + re.escape(name) + tail
311 elif kind == 'relre':
316 elif kind == 'relre':
312 if name.startswith('^'):
317 if name.startswith('^'):
313 return name
318 return name
314 return '.*' + name
319 return '.*' + name
315 return head + globre(name, '', tail)
320 return head + globre(name, '', tail)
316
321
317 def matchfn(pats, tail):
322 def matchfn(pats, tail):
318 """build a matching function from a set of patterns"""
323 """build a matching function from a set of patterns"""
319 if not pats:
324 if not pats:
320 return
325 return
321 matches = []
326 matches = []
322 for k, p in pats:
327 for k, p in pats:
323 try:
328 try:
324 pat = '(?:%s)' % regex(k, p, tail)
329 pat = '(?:%s)' % regex(k, p, tail)
325 matches.append(re.compile(pat).match)
330 matches.append(re.compile(pat).match)
326 except re.error:
331 except re.error:
327 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
332 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
328 else: raise Abort("invalid pattern (%s): %s" % (k, p))
333 else: raise Abort("invalid pattern (%s): %s" % (k, p))
329
334
330 def buildfn(text):
335 def buildfn(text):
331 for m in matches:
336 for m in matches:
332 r = m(text)
337 r = m(text)
333 if r:
338 if r:
334 return r
339 return r
335
340
336 return buildfn
341 return buildfn
337
342
338 def globprefix(pat):
343 def globprefix(pat):
339 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
344 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
340 root = []
345 root = []
341 for p in pat.split(os.sep):
346 for p in pat.split(os.sep):
342 if contains_glob(p): break
347 if contains_glob(p): break
343 root.append(p)
348 root.append(p)
344 return '/'.join(root)
349 return '/'.join(root)
345
350
346 pats = []
351 pats = []
347 files = []
352 files = []
348 roots = []
353 roots = []
349 for kind, name in [patkind(p, dflt_pat) for p in names]:
354 for kind, name in [patkind(p, dflt_pat) for p in names]:
350 if kind in ('glob', 'relpath'):
355 if kind in ('glob', 'relpath'):
351 name = canonpath(canonroot, cwd, name)
356 name = canonpath(canonroot, cwd, name)
352 if name == '':
357 if name == '':
353 kind, name = 'glob', '**'
358 kind, name = 'glob', '**'
354 if kind in ('glob', 'path', 're'):
359 if kind in ('glob', 'path', 're'):
355 pats.append((kind, name))
360 pats.append((kind, name))
356 if kind == 'glob':
361 if kind == 'glob':
357 root = globprefix(name)
362 root = globprefix(name)
358 if root: roots.append(root)
363 if root: roots.append(root)
359 elif kind == 'relpath':
364 elif kind == 'relpath':
360 files.append((kind, name))
365 files.append((kind, name))
361 roots.append(name)
366 roots.append(name)
362
367
363 patmatch = matchfn(pats, '$') or always
368 patmatch = matchfn(pats, '$') or always
364 filematch = matchfn(files, '(?:/|$)') or always
369 filematch = matchfn(files, '(?:/|$)') or always
365 incmatch = always
370 incmatch = always
366 if inc:
371 if inc:
367 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
372 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
368 incmatch = matchfn(inckinds, '(?:/|$)')
373 incmatch = matchfn(inckinds, '(?:/|$)')
369 excmatch = lambda fn: False
374 excmatch = lambda fn: False
370 if exc:
375 if exc:
371 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
376 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
372 excmatch = matchfn(exckinds, '(?:/|$)')
377 excmatch = matchfn(exckinds, '(?:/|$)')
373
378
374 return (roots,
379 return (roots,
375 lambda fn: (incmatch(fn) and not excmatch(fn) and
380 lambda fn: (incmatch(fn) and not excmatch(fn) and
376 (fn.endswith('/') or
381 (fn.endswith('/') or
377 (not pats and not files) or
382 (not pats and not files) or
378 (pats and patmatch(fn)) or
383 (pats and patmatch(fn)) or
379 (files and filematch(fn)))),
384 (files and filematch(fn)))),
380 (inc or exc or (pats and pats != [('glob', '**')])) and True)
385 (inc or exc or (pats and pats != [('glob', '**')])) and True)
381
386
382 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
387 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
383 '''enhanced shell command execution.
388 '''enhanced shell command execution.
384 run with environment maybe modified, maybe in different dir.
389 run with environment maybe modified, maybe in different dir.
385
390
386 if command fails and onerr is None, return status. if ui object,
391 if command fails and onerr is None, return status. if ui object,
387 print error message and return status, else raise onerr object as
392 print error message and return status, else raise onerr object as
388 exception.'''
393 exception.'''
389 def py2shell(val):
394 def py2shell(val):
390 'convert python object into string that is useful to shell'
395 'convert python object into string that is useful to shell'
391 if val in (None, False):
396 if val in (None, False):
392 return '0'
397 return '0'
393 if val == True:
398 if val == True:
394 return '1'
399 return '1'
395 return str(val)
400 return str(val)
396 oldenv = {}
401 oldenv = {}
397 for k in environ:
402 for k in environ:
398 oldenv[k] = os.environ.get(k)
403 oldenv[k] = os.environ.get(k)
399 if cwd is not None:
404 if cwd is not None:
400 oldcwd = os.getcwd()
405 oldcwd = os.getcwd()
401 try:
406 try:
402 for k, v in environ.iteritems():
407 for k, v in environ.iteritems():
403 os.environ[k] = py2shell(v)
408 os.environ[k] = py2shell(v)
404 if cwd is not None and oldcwd != cwd:
409 if cwd is not None and oldcwd != cwd:
405 os.chdir(cwd)
410 os.chdir(cwd)
406 rc = os.system(cmd)
411 rc = os.system(cmd)
407 if rc and onerr:
412 if rc and onerr:
408 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
413 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
409 explain_exit(rc)[0])
414 explain_exit(rc)[0])
410 if errprefix:
415 if errprefix:
411 errmsg = '%s: %s' % (errprefix, errmsg)
416 errmsg = '%s: %s' % (errprefix, errmsg)
412 try:
417 try:
413 onerr.warn(errmsg + '\n')
418 onerr.warn(errmsg + '\n')
414 except AttributeError:
419 except AttributeError:
415 raise onerr(errmsg)
420 raise onerr(errmsg)
416 return rc
421 return rc
417 finally:
422 finally:
418 for k, v in oldenv.iteritems():
423 for k, v in oldenv.iteritems():
419 if v is None:
424 if v is None:
420 del os.environ[k]
425 del os.environ[k]
421 else:
426 else:
422 os.environ[k] = v
427 os.environ[k] = v
423 if cwd is not None and oldcwd != cwd:
428 if cwd is not None and oldcwd != cwd:
424 os.chdir(oldcwd)
429 os.chdir(oldcwd)
425
430
426 def rename(src, dst):
431 def rename(src, dst):
427 """forcibly rename a file"""
432 """forcibly rename a file"""
428 try:
433 try:
429 os.rename(src, dst)
434 os.rename(src, dst)
430 except OSError, err:
435 except OSError, err:
431 # on windows, rename to existing file is not allowed, so we
436 # on windows, rename to existing file is not allowed, so we
432 # must delete destination first. but if file is open, unlink
437 # must delete destination first. but if file is open, unlink
433 # schedules it for delete but does not delete it. rename
438 # schedules it for delete but does not delete it. rename
434 # happens immediately even for open files, so we create
439 # happens immediately even for open files, so we create
435 # temporary file, delete it, rename destination to that name,
440 # temporary file, delete it, rename destination to that name,
436 # then delete that. then rename is safe to do.
441 # then delete that. then rename is safe to do.
437 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
442 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
438 os.close(fd)
443 os.close(fd)
439 os.unlink(temp)
444 os.unlink(temp)
440 os.rename(dst, temp)
445 os.rename(dst, temp)
441 os.unlink(temp)
446 os.unlink(temp)
442 os.rename(src, dst)
447 os.rename(src, dst)
443
448
444 def unlink(f):
449 def unlink(f):
445 """unlink and remove the directory if it is empty"""
450 """unlink and remove the directory if it is empty"""
446 os.unlink(f)
451 os.unlink(f)
447 # try removing directories that might now be empty
452 # try removing directories that might now be empty
448 try:
453 try:
449 os.removedirs(os.path.dirname(f))
454 os.removedirs(os.path.dirname(f))
450 except OSError:
455 except OSError:
451 pass
456 pass
452
457
453 def copyfiles(src, dst, hardlink=None):
458 def copyfiles(src, dst, hardlink=None):
454 """Copy a directory tree using hardlinks if possible"""
459 """Copy a directory tree using hardlinks if possible"""
455
460
456 if hardlink is None:
461 if hardlink is None:
457 hardlink = (os.stat(src).st_dev ==
462 hardlink = (os.stat(src).st_dev ==
458 os.stat(os.path.dirname(dst)).st_dev)
463 os.stat(os.path.dirname(dst)).st_dev)
459
464
460 if os.path.isdir(src):
465 if os.path.isdir(src):
461 os.mkdir(dst)
466 os.mkdir(dst)
462 for name in os.listdir(src):
467 for name in os.listdir(src):
463 srcname = os.path.join(src, name)
468 srcname = os.path.join(src, name)
464 dstname = os.path.join(dst, name)
469 dstname = os.path.join(dst, name)
465 copyfiles(srcname, dstname, hardlink)
470 copyfiles(srcname, dstname, hardlink)
466 else:
471 else:
467 if hardlink:
472 if hardlink:
468 try:
473 try:
469 os_link(src, dst)
474 os_link(src, dst)
470 except (IOError, OSError):
475 except (IOError, OSError):
471 hardlink = False
476 hardlink = False
472 shutil.copy(src, dst)
477 shutil.copy(src, dst)
473 else:
478 else:
474 shutil.copy(src, dst)
479 shutil.copy(src, dst)
475
480
476 def audit_path(path):
481 def audit_path(path):
477 """Abort if path contains dangerous components"""
482 """Abort if path contains dangerous components"""
478 parts = os.path.normcase(path).split(os.sep)
483 parts = os.path.normcase(path).split(os.sep)
479 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
484 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
480 or os.pardir in parts):
485 or os.pardir in parts):
481 raise Abort(_("path contains illegal component: %s\n") % path)
486 raise Abort(_("path contains illegal component: %s\n") % path)
482
487
483 def _makelock_file(info, pathname):
488 def _makelock_file(info, pathname):
484 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
489 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
485 os.write(ld, info)
490 os.write(ld, info)
486 os.close(ld)
491 os.close(ld)
487
492
488 def _readlock_file(pathname):
493 def _readlock_file(pathname):
489 return posixfile(pathname).read()
494 return posixfile(pathname).read()
490
495
491 def nlinks(pathname):
496 def nlinks(pathname):
492 """Return number of hardlinks for the given file."""
497 """Return number of hardlinks for the given file."""
493 return os.lstat(pathname).st_nlink
498 return os.lstat(pathname).st_nlink
494
499
495 if hasattr(os, 'link'):
500 if hasattr(os, 'link'):
496 os_link = os.link
501 os_link = os.link
497 else:
502 else:
498 def os_link(src, dst):
503 def os_link(src, dst):
499 raise OSError(0, _("Hardlinks not supported"))
504 raise OSError(0, _("Hardlinks not supported"))
500
505
501 def fstat(fp):
506 def fstat(fp):
502 '''stat file object that may not have fileno method.'''
507 '''stat file object that may not have fileno method.'''
503 try:
508 try:
504 return os.fstat(fp.fileno())
509 return os.fstat(fp.fileno())
505 except AttributeError:
510 except AttributeError:
506 return os.stat(fp.name)
511 return os.stat(fp.name)
507
512
508 posixfile = file
513 posixfile = file
509
514
510 def is_win_9x():
515 def is_win_9x():
511 '''return true if run on windows 95, 98 or me.'''
516 '''return true if run on windows 95, 98 or me.'''
512 try:
517 try:
513 return sys.getwindowsversion()[3] == 1
518 return sys.getwindowsversion()[3] == 1
514 except AttributeError:
519 except AttributeError:
515 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
520 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
516
521
517 getuser_fallback = None
522 getuser_fallback = None
518
523
519 def getuser():
524 def getuser():
520 '''return name of current user'''
525 '''return name of current user'''
521 try:
526 try:
522 return getpass.getuser()
527 return getpass.getuser()
523 except ImportError:
528 except ImportError:
524 # import of pwd will fail on windows - try fallback
529 # import of pwd will fail on windows - try fallback
525 if getuser_fallback:
530 if getuser_fallback:
526 return getuser_fallback()
531 return getuser_fallback()
527 # raised if win32api not available
532 # raised if win32api not available
528 raise Abort(_('user name not available - set USERNAME '
533 raise Abort(_('user name not available - set USERNAME '
529 'environment variable'))
534 'environment variable'))
530
535
531 # Platform specific variants
536 # Platform specific variants
532 if os.name == 'nt':
537 if os.name == 'nt':
533 demandload(globals(), "msvcrt")
538 demandload(globals(), "msvcrt")
534 nulldev = 'NUL:'
539 nulldev = 'NUL:'
535
540
536 class winstdout:
541 class winstdout:
537 '''stdout on windows misbehaves if sent through a pipe'''
542 '''stdout on windows misbehaves if sent through a pipe'''
538
543
539 def __init__(self, fp):
544 def __init__(self, fp):
540 self.fp = fp
545 self.fp = fp
541
546
542 def __getattr__(self, key):
547 def __getattr__(self, key):
543 return getattr(self.fp, key)
548 return getattr(self.fp, key)
544
549
545 def close(self):
550 def close(self):
546 try:
551 try:
547 self.fp.close()
552 self.fp.close()
548 except: pass
553 except: pass
549
554
550 def write(self, s):
555 def write(self, s):
551 try:
556 try:
552 return self.fp.write(s)
557 return self.fp.write(s)
553 except IOError, inst:
558 except IOError, inst:
554 if inst.errno != 0: raise
559 if inst.errno != 0: raise
555 self.close()
560 self.close()
556 raise IOError(errno.EPIPE, 'Broken pipe')
561 raise IOError(errno.EPIPE, 'Broken pipe')
557
562
558 sys.stdout = winstdout(sys.stdout)
563 sys.stdout = winstdout(sys.stdout)
559
564
560 def system_rcpath():
565 def system_rcpath():
561 try:
566 try:
562 return system_rcpath_win32()
567 return system_rcpath_win32()
563 except:
568 except:
564 return [r'c:\mercurial\mercurial.ini']
569 return [r'c:\mercurial\mercurial.ini']
565
570
566 def os_rcpath():
571 def os_rcpath():
567 '''return default os-specific hgrc search path'''
572 '''return default os-specific hgrc search path'''
568 path = system_rcpath()
573 path = system_rcpath()
569 path.append(user_rcpath())
574 path.append(user_rcpath())
570 userprofile = os.environ.get('USERPROFILE')
575 userprofile = os.environ.get('USERPROFILE')
571 if userprofile:
576 if userprofile:
572 path.append(os.path.join(userprofile, 'mercurial.ini'))
577 path.append(os.path.join(userprofile, 'mercurial.ini'))
573 return path
578 return path
574
579
575 def user_rcpath():
580 def user_rcpath():
576 '''return os-specific hgrc search path to the user dir'''
581 '''return os-specific hgrc search path to the user dir'''
577 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
582 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
578
583
579 def parse_patch_output(output_line):
584 def parse_patch_output(output_line):
580 """parses the output produced by patch and returns the file name"""
585 """parses the output produced by patch and returns the file name"""
581 pf = output_line[14:]
586 pf = output_line[14:]
582 if pf[0] == '`':
587 if pf[0] == '`':
583 pf = pf[1:-1] # Remove the quotes
588 pf = pf[1:-1] # Remove the quotes
584 return pf
589 return pf
585
590
586 def testpid(pid):
591 def testpid(pid):
587 '''return False if pid dead, True if running or not known'''
592 '''return False if pid dead, True if running or not known'''
588 return True
593 return True
589
594
590 def is_exec(f, last):
595 def is_exec(f, last):
591 return last
596 return last
592
597
593 def set_exec(f, mode):
598 def set_exec(f, mode):
594 pass
599 pass
595
600
596 def set_binary(fd):
601 def set_binary(fd):
597 msvcrt.setmode(fd.fileno(), os.O_BINARY)
602 msvcrt.setmode(fd.fileno(), os.O_BINARY)
598
603
599 def pconvert(path):
604 def pconvert(path):
600 return path.replace("\\", "/")
605 return path.replace("\\", "/")
601
606
602 def localpath(path):
607 def localpath(path):
603 return path.replace('/', '\\')
608 return path.replace('/', '\\')
604
609
605 def normpath(path):
610 def normpath(path):
606 return pconvert(os.path.normpath(path))
611 return pconvert(os.path.normpath(path))
607
612
608 makelock = _makelock_file
613 makelock = _makelock_file
609 readlock = _readlock_file
614 readlock = _readlock_file
610
615
611 def samestat(s1, s2):
616 def samestat(s1, s2):
612 return False
617 return False
613
618
614 def shellquote(s):
619 def shellquote(s):
615 return '"%s"' % s.replace('"', '\\"')
620 return '"%s"' % s.replace('"', '\\"')
616
621
617 def explain_exit(code):
622 def explain_exit(code):
618 return _("exited with status %d") % code, code
623 return _("exited with status %d") % code, code
619
624
620 try:
625 try:
621 # override functions with win32 versions if possible
626 # override functions with win32 versions if possible
622 from util_win32 import *
627 from util_win32 import *
623 if not is_win_9x():
628 if not is_win_9x():
624 posixfile = posixfile_nt
629 posixfile = posixfile_nt
625 except ImportError:
630 except ImportError:
626 pass
631 pass
627
632
628 else:
633 else:
629 nulldev = '/dev/null'
634 nulldev = '/dev/null'
630
635
631 def rcfiles(path):
636 def rcfiles(path):
632 rcs = [os.path.join(path, 'hgrc')]
637 rcs = [os.path.join(path, 'hgrc')]
633 rcdir = os.path.join(path, 'hgrc.d')
638 rcdir = os.path.join(path, 'hgrc.d')
634 try:
639 try:
635 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
640 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
636 if f.endswith(".rc")])
641 if f.endswith(".rc")])
637 except OSError:
642 except OSError:
638 pass
643 pass
639 return rcs
644 return rcs
640
645
641 def os_rcpath():
646 def os_rcpath():
642 '''return default os-specific hgrc search path'''
647 '''return default os-specific hgrc search path'''
643 path = []
648 path = []
644 # old mod_python does not set sys.argv
649 # old mod_python does not set sys.argv
645 if len(getattr(sys, 'argv', [])) > 0:
650 if len(getattr(sys, 'argv', [])) > 0:
646 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
651 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
647 '/../etc/mercurial'))
652 '/../etc/mercurial'))
648 path.extend(rcfiles('/etc/mercurial'))
653 path.extend(rcfiles('/etc/mercurial'))
649 path.append(os.path.expanduser('~/.hgrc'))
654 path.append(os.path.expanduser('~/.hgrc'))
650 path = [os.path.normpath(f) for f in path]
655 path = [os.path.normpath(f) for f in path]
651 return path
656 return path
652
657
653 def parse_patch_output(output_line):
658 def parse_patch_output(output_line):
654 """parses the output produced by patch and returns the file name"""
659 """parses the output produced by patch and returns the file name"""
655 pf = output_line[14:]
660 pf = output_line[14:]
656 if pf.startswith("'") and pf.endswith("'") and " " in pf:
661 if pf.startswith("'") and pf.endswith("'") and " " in pf:
657 pf = pf[1:-1] # Remove the quotes
662 pf = pf[1:-1] # Remove the quotes
658 return pf
663 return pf
659
664
660 def is_exec(f, last):
665 def is_exec(f, last):
661 """check whether a file is executable"""
666 """check whether a file is executable"""
662 return (os.lstat(f).st_mode & 0100 != 0)
667 return (os.lstat(f).st_mode & 0100 != 0)
663
668
664 def set_exec(f, mode):
669 def set_exec(f, mode):
665 s = os.lstat(f).st_mode
670 s = os.lstat(f).st_mode
666 if (s & 0100 != 0) == mode:
671 if (s & 0100 != 0) == mode:
667 return
672 return
668 if mode:
673 if mode:
669 # Turn on +x for every +r bit when making a file executable
674 # Turn on +x for every +r bit when making a file executable
670 # and obey umask.
675 # and obey umask.
671 umask = os.umask(0)
676 umask = os.umask(0)
672 os.umask(umask)
677 os.umask(umask)
673 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
678 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
674 else:
679 else:
675 os.chmod(f, s & 0666)
680 os.chmod(f, s & 0666)
676
681
677 def set_binary(fd):
682 def set_binary(fd):
678 pass
683 pass
679
684
680 def pconvert(path):
685 def pconvert(path):
681 return path
686 return path
682
687
683 def localpath(path):
688 def localpath(path):
684 return path
689 return path
685
690
686 normpath = os.path.normpath
691 normpath = os.path.normpath
687 samestat = os.path.samestat
692 samestat = os.path.samestat
688
693
689 def makelock(info, pathname):
694 def makelock(info, pathname):
690 try:
695 try:
691 os.symlink(info, pathname)
696 os.symlink(info, pathname)
692 except OSError, why:
697 except OSError, why:
693 if why.errno == errno.EEXIST:
698 if why.errno == errno.EEXIST:
694 raise
699 raise
695 else:
700 else:
696 _makelock_file(info, pathname)
701 _makelock_file(info, pathname)
697
702
698 def readlock(pathname):
703 def readlock(pathname):
699 try:
704 try:
700 return os.readlink(pathname)
705 return os.readlink(pathname)
701 except OSError, why:
706 except OSError, why:
702 if why.errno == errno.EINVAL:
707 if why.errno == errno.EINVAL:
703 return _readlock_file(pathname)
708 return _readlock_file(pathname)
704 else:
709 else:
705 raise
710 raise
706
711
707 def shellquote(s):
712 def shellquote(s):
708 return "'%s'" % s.replace("'", "'\\''")
713 return "'%s'" % s.replace("'", "'\\''")
709
714
710 def testpid(pid):
715 def testpid(pid):
711 '''return False if pid dead, True if running or not sure'''
716 '''return False if pid dead, True if running or not sure'''
712 try:
717 try:
713 os.kill(pid, 0)
718 os.kill(pid, 0)
714 return True
719 return True
715 except OSError, inst:
720 except OSError, inst:
716 return inst.errno != errno.ESRCH
721 return inst.errno != errno.ESRCH
717
722
718 def explain_exit(code):
723 def explain_exit(code):
719 """return a 2-tuple (desc, code) describing a process's status"""
724 """return a 2-tuple (desc, code) describing a process's status"""
720 if os.WIFEXITED(code):
725 if os.WIFEXITED(code):
721 val = os.WEXITSTATUS(code)
726 val = os.WEXITSTATUS(code)
722 return _("exited with status %d") % val, val
727 return _("exited with status %d") % val, val
723 elif os.WIFSIGNALED(code):
728 elif os.WIFSIGNALED(code):
724 val = os.WTERMSIG(code)
729 val = os.WTERMSIG(code)
725 return _("killed by signal %d") % val, val
730 return _("killed by signal %d") % val, val
726 elif os.WIFSTOPPED(code):
731 elif os.WIFSTOPPED(code):
727 val = os.WSTOPSIG(code)
732 val = os.WSTOPSIG(code)
728 return _("stopped by signal %d") % val, val
733 return _("stopped by signal %d") % val, val
729 raise ValueError(_("invalid exit code"))
734 raise ValueError(_("invalid exit code"))
730
735
731 def opener(base, audit=True):
736 def opener(base, audit=True):
732 """
737 """
733 return a function that opens files relative to base
738 return a function that opens files relative to base
734
739
735 this function is used to hide the details of COW semantics and
740 this function is used to hide the details of COW semantics and
736 remote file access from higher level code.
741 remote file access from higher level code.
737 """
742 """
738 p = base
743 p = base
739 audit_p = audit
744 audit_p = audit
740
745
741 def mktempcopy(name):
746 def mktempcopy(name):
742 d, fn = os.path.split(name)
747 d, fn = os.path.split(name)
743 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
748 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
744 os.close(fd)
749 os.close(fd)
745 ofp = posixfile(temp, "wb")
750 ofp = posixfile(temp, "wb")
746 try:
751 try:
747 try:
752 try:
748 ifp = posixfile(name, "rb")
753 ifp = posixfile(name, "rb")
749 except IOError, inst:
754 except IOError, inst:
750 if not getattr(inst, 'filename', None):
755 if not getattr(inst, 'filename', None):
751 inst.filename = name
756 inst.filename = name
752 raise
757 raise
753 for chunk in filechunkiter(ifp):
758 for chunk in filechunkiter(ifp):
754 ofp.write(chunk)
759 ofp.write(chunk)
755 ifp.close()
760 ifp.close()
756 ofp.close()
761 ofp.close()
757 except:
762 except:
758 try: os.unlink(temp)
763 try: os.unlink(temp)
759 except: pass
764 except: pass
760 raise
765 raise
761 st = os.lstat(name)
766 st = os.lstat(name)
762 os.chmod(temp, st.st_mode)
767 os.chmod(temp, st.st_mode)
763 return temp
768 return temp
764
769
765 class atomictempfile(posixfile):
770 class atomictempfile(posixfile):
766 """the file will only be copied when rename is called"""
771 """the file will only be copied when rename is called"""
767 def __init__(self, name, mode):
772 def __init__(self, name, mode):
768 self.__name = name
773 self.__name = name
769 self.temp = mktempcopy(name)
774 self.temp = mktempcopy(name)
770 posixfile.__init__(self, self.temp, mode)
775 posixfile.__init__(self, self.temp, mode)
771 def rename(self):
776 def rename(self):
772 if not self.closed:
777 if not self.closed:
773 posixfile.close(self)
778 posixfile.close(self)
774 rename(self.temp, localpath(self.__name))
779 rename(self.temp, localpath(self.__name))
775 def __del__(self):
780 def __del__(self):
776 if not self.closed:
781 if not self.closed:
777 try:
782 try:
778 os.unlink(self.temp)
783 os.unlink(self.temp)
779 except: pass
784 except: pass
780 posixfile.close(self)
785 posixfile.close(self)
781
786
782 class atomicfile(atomictempfile):
787 class atomicfile(atomictempfile):
783 """the file will only be copied on close"""
788 """the file will only be copied on close"""
784 def __init__(self, name, mode):
789 def __init__(self, name, mode):
785 atomictempfile.__init__(self, name, mode)
790 atomictempfile.__init__(self, name, mode)
786 def close(self):
791 def close(self):
787 self.rename()
792 self.rename()
788 def __del__(self):
793 def __del__(self):
789 self.rename()
794 self.rename()
790
795
791 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
796 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
792 if audit_p:
797 if audit_p:
793 audit_path(path)
798 audit_path(path)
794 f = os.path.join(p, path)
799 f = os.path.join(p, path)
795
800
796 if not text:
801 if not text:
797 mode += "b" # for that other OS
802 mode += "b" # for that other OS
798
803
799 if mode[0] != "r":
804 if mode[0] != "r":
800 try:
805 try:
801 nlink = nlinks(f)
806 nlink = nlinks(f)
802 except OSError:
807 except OSError:
803 d = os.path.dirname(f)
808 d = os.path.dirname(f)
804 if not os.path.isdir(d):
809 if not os.path.isdir(d):
805 os.makedirs(d)
810 os.makedirs(d)
806 else:
811 else:
807 if atomic:
812 if atomic:
808 return atomicfile(f, mode)
813 return atomicfile(f, mode)
809 elif atomictemp:
814 elif atomictemp:
810 return atomictempfile(f, mode)
815 return atomictempfile(f, mode)
811 if nlink > 1:
816 if nlink > 1:
812 rename(mktempcopy(f), f)
817 rename(mktempcopy(f), f)
813 return posixfile(f, mode)
818 return posixfile(f, mode)
814
819
815 return o
820 return o
816
821
817 class chunkbuffer(object):
822 class chunkbuffer(object):
818 """Allow arbitrary sized chunks of data to be efficiently read from an
823 """Allow arbitrary sized chunks of data to be efficiently read from an
819 iterator over chunks of arbitrary size."""
824 iterator over chunks of arbitrary size."""
820
825
821 def __init__(self, in_iter, targetsize = 2**16):
826 def __init__(self, in_iter, targetsize = 2**16):
822 """in_iter is the iterator that's iterating over the input chunks.
827 """in_iter is the iterator that's iterating over the input chunks.
823 targetsize is how big a buffer to try to maintain."""
828 targetsize is how big a buffer to try to maintain."""
824 self.in_iter = iter(in_iter)
829 self.in_iter = iter(in_iter)
825 self.buf = ''
830 self.buf = ''
826 self.targetsize = int(targetsize)
831 self.targetsize = int(targetsize)
827 if self.targetsize <= 0:
832 if self.targetsize <= 0:
828 raise ValueError(_("targetsize must be greater than 0, was %d") %
833 raise ValueError(_("targetsize must be greater than 0, was %d") %
829 targetsize)
834 targetsize)
830 self.iterempty = False
835 self.iterempty = False
831
836
832 def fillbuf(self):
837 def fillbuf(self):
833 """Ignore target size; read every chunk from iterator until empty."""
838 """Ignore target size; read every chunk from iterator until empty."""
834 if not self.iterempty:
839 if not self.iterempty:
835 collector = cStringIO.StringIO()
840 collector = cStringIO.StringIO()
836 collector.write(self.buf)
841 collector.write(self.buf)
837 for ch in self.in_iter:
842 for ch in self.in_iter:
838 collector.write(ch)
843 collector.write(ch)
839 self.buf = collector.getvalue()
844 self.buf = collector.getvalue()
840 self.iterempty = True
845 self.iterempty = True
841
846
842 def read(self, l):
847 def read(self, l):
843 """Read L bytes of data from the iterator of chunks of data.
848 """Read L bytes of data from the iterator of chunks of data.
844 Returns less than L bytes if the iterator runs dry."""
849 Returns less than L bytes if the iterator runs dry."""
845 if l > len(self.buf) and not self.iterempty:
850 if l > len(self.buf) and not self.iterempty:
846 # Clamp to a multiple of self.targetsize
851 # Clamp to a multiple of self.targetsize
847 targetsize = self.targetsize * ((l // self.targetsize) + 1)
852 targetsize = self.targetsize * ((l // self.targetsize) + 1)
848 collector = cStringIO.StringIO()
853 collector = cStringIO.StringIO()
849 collector.write(self.buf)
854 collector.write(self.buf)
850 collected = len(self.buf)
855 collected = len(self.buf)
851 for chunk in self.in_iter:
856 for chunk in self.in_iter:
852 collector.write(chunk)
857 collector.write(chunk)
853 collected += len(chunk)
858 collected += len(chunk)
854 if collected >= targetsize:
859 if collected >= targetsize:
855 break
860 break
856 if collected < targetsize:
861 if collected < targetsize:
857 self.iterempty = True
862 self.iterempty = True
858 self.buf = collector.getvalue()
863 self.buf = collector.getvalue()
859 s, self.buf = self.buf[:l], buffer(self.buf, l)
864 s, self.buf = self.buf[:l], buffer(self.buf, l)
860 return s
865 return s
861
866
862 def filechunkiter(f, size=65536, limit=None):
867 def filechunkiter(f, size=65536, limit=None):
863 """Create a generator that produces the data in the file size
868 """Create a generator that produces the data in the file size
864 (default 65536) bytes at a time, up to optional limit (default is
869 (default 65536) bytes at a time, up to optional limit (default is
865 to read all data). Chunks may be less than size bytes if the
870 to read all data). Chunks may be less than size bytes if the
866 chunk is the last chunk in the file, or the file is a socket or
871 chunk is the last chunk in the file, or the file is a socket or
867 some other type of file that sometimes reads less data than is
872 some other type of file that sometimes reads less data than is
868 requested."""
873 requested."""
869 assert size >= 0
874 assert size >= 0
870 assert limit is None or limit >= 0
875 assert limit is None or limit >= 0
871 while True:
876 while True:
872 if limit is None: nbytes = size
877 if limit is None: nbytes = size
873 else: nbytes = min(limit, size)
878 else: nbytes = min(limit, size)
874 s = nbytes and f.read(nbytes)
879 s = nbytes and f.read(nbytes)
875 if not s: break
880 if not s: break
876 if limit: limit -= len(s)
881 if limit: limit -= len(s)
877 yield s
882 yield s
878
883
879 def makedate():
884 def makedate():
880 lt = time.localtime()
885 lt = time.localtime()
881 if lt[8] == 1 and time.daylight:
886 if lt[8] == 1 and time.daylight:
882 tz = time.altzone
887 tz = time.altzone
883 else:
888 else:
884 tz = time.timezone
889 tz = time.timezone
885 return time.mktime(lt), tz
890 return time.mktime(lt), tz
886
891
887 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
892 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
888 """represent a (unixtime, offset) tuple as a localized time.
893 """represent a (unixtime, offset) tuple as a localized time.
889 unixtime is seconds since the epoch, and offset is the time zone's
894 unixtime is seconds since the epoch, and offset is the time zone's
890 number of seconds away from UTC. if timezone is false, do not
895 number of seconds away from UTC. if timezone is false, do not
891 append time zone to string."""
896 append time zone to string."""
892 t, tz = date or makedate()
897 t, tz = date or makedate()
893 s = time.strftime(format, time.gmtime(float(t) - tz))
898 s = time.strftime(format, time.gmtime(float(t) - tz))
894 if timezone:
899 if timezone:
895 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
900 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
896 return s
901 return s
897
902
898 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
903 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
899 """parse a localized time string and return a (unixtime, offset) tuple.
904 """parse a localized time string and return a (unixtime, offset) tuple.
900 if the string cannot be parsed, ValueError is raised."""
905 if the string cannot be parsed, ValueError is raised."""
901 def hastimezone(string):
906 def hastimezone(string):
902 return (string[-4:].isdigit() and
907 return (string[-4:].isdigit() and
903 (string[-5] == '+' or string[-5] == '-') and
908 (string[-5] == '+' or string[-5] == '-') and
904 string[-6].isspace())
909 string[-6].isspace())
905
910
906 # NOTE: unixtime = localunixtime + offset
911 # NOTE: unixtime = localunixtime + offset
907 if hastimezone(string):
912 if hastimezone(string):
908 date, tz = string[:-6], string[-5:]
913 date, tz = string[:-6], string[-5:]
909 tz = int(tz)
914 tz = int(tz)
910 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
915 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
911 else:
916 else:
912 date, offset = string, None
917 date, offset = string, None
913 timetuple = time.strptime(date, format)
918 timetuple = time.strptime(date, format)
914 localunixtime = int(calendar.timegm(timetuple))
919 localunixtime = int(calendar.timegm(timetuple))
915 if offset is None:
920 if offset is None:
916 # local timezone
921 # local timezone
917 unixtime = int(time.mktime(timetuple))
922 unixtime = int(time.mktime(timetuple))
918 offset = unixtime - localunixtime
923 offset = unixtime - localunixtime
919 else:
924 else:
920 unixtime = localunixtime + offset
925 unixtime = localunixtime + offset
921 return unixtime, offset
926 return unixtime, offset
922
927
923 def parsedate(string, formats=None):
928 def parsedate(string, formats=None):
924 """parse a localized time string and return a (unixtime, offset) tuple.
929 """parse a localized time string and return a (unixtime, offset) tuple.
925 The date may be a "unixtime offset" string or in one of the specified
930 The date may be a "unixtime offset" string or in one of the specified
926 formats."""
931 formats."""
927 if not formats:
932 if not formats:
928 formats = defaultdateformats
933 formats = defaultdateformats
929 try:
934 try:
930 when, offset = map(int, string.split(' '))
935 when, offset = map(int, string.split(' '))
931 except ValueError:
936 except ValueError:
932 for format in formats:
937 for format in formats:
933 try:
938 try:
934 when, offset = strdate(string, format)
939 when, offset = strdate(string, format)
935 except ValueError:
940 except ValueError:
936 pass
941 pass
937 else:
942 else:
938 break
943 break
939 else:
944 else:
940 raise ValueError(_('invalid date: %r '
945 raise ValueError(_('invalid date: %r '
941 'see hg(1) manual page for details')
946 'see hg(1) manual page for details')
942 % string)
947 % string)
943 # validate explicit (probably user-specified) date and
948 # validate explicit (probably user-specified) date and
944 # time zone offset. values must fit in signed 32 bits for
949 # time zone offset. values must fit in signed 32 bits for
945 # current 32-bit linux runtimes. timezones go from UTC-12
950 # current 32-bit linux runtimes. timezones go from UTC-12
946 # to UTC+14
951 # to UTC+14
947 if abs(when) > 0x7fffffff:
952 if abs(when) > 0x7fffffff:
948 raise ValueError(_('date exceeds 32 bits: %d') % when)
953 raise ValueError(_('date exceeds 32 bits: %d') % when)
949 if offset < -50400 or offset > 43200:
954 if offset < -50400 or offset > 43200:
950 raise ValueError(_('impossible time zone offset: %d') % offset)
955 raise ValueError(_('impossible time zone offset: %d') % offset)
951 return when, offset
956 return when, offset
952
957
953 def shortuser(user):
958 def shortuser(user):
954 """Return a short representation of a user name or email address."""
959 """Return a short representation of a user name or email address."""
955 f = user.find('@')
960 f = user.find('@')
956 if f >= 0:
961 if f >= 0:
957 user = user[:f]
962 user = user[:f]
958 f = user.find('<')
963 f = user.find('<')
959 if f >= 0:
964 if f >= 0:
960 user = user[f+1:]
965 user = user[f+1:]
961 f = user.find(' ')
966 f = user.find(' ')
962 if f >= 0:
967 if f >= 0:
963 user = user[:f]
968 user = user[:f]
964 return user
969 return user
965
970
966 def walkrepos(path):
971 def walkrepos(path):
967 '''yield every hg repository under path, recursively.'''
972 '''yield every hg repository under path, recursively.'''
968 def errhandler(err):
973 def errhandler(err):
969 if err.filename == path:
974 if err.filename == path:
970 raise err
975 raise err
971
976
972 for root, dirs, files in os.walk(path, onerror=errhandler):
977 for root, dirs, files in os.walk(path, onerror=errhandler):
973 for d in dirs:
978 for d in dirs:
974 if d == '.hg':
979 if d == '.hg':
975 yield root
980 yield root
976 dirs[:] = []
981 dirs[:] = []
977 break
982 break
978
983
979 _rcpath = None
984 _rcpath = None
980
985
981 def rcpath():
986 def rcpath():
982 '''return hgrc search path. if env var HGRCPATH is set, use it.
987 '''return hgrc search path. if env var HGRCPATH is set, use it.
983 for each item in path, if directory, use files ending in .rc,
988 for each item in path, if directory, use files ending in .rc,
984 else use item.
989 else use item.
985 make HGRCPATH empty to only look in .hg/hgrc of current repo.
990 make HGRCPATH empty to only look in .hg/hgrc of current repo.
986 if no HGRCPATH, use default os-specific path.'''
991 if no HGRCPATH, use default os-specific path.'''
987 global _rcpath
992 global _rcpath
988 if _rcpath is None:
993 if _rcpath is None:
989 if 'HGRCPATH' in os.environ:
994 if 'HGRCPATH' in os.environ:
990 _rcpath = []
995 _rcpath = []
991 for p in os.environ['HGRCPATH'].split(os.pathsep):
996 for p in os.environ['HGRCPATH'].split(os.pathsep):
992 if not p: continue
997 if not p: continue
993 if os.path.isdir(p):
998 if os.path.isdir(p):
994 for f in os.listdir(p):
999 for f in os.listdir(p):
995 if f.endswith('.rc'):
1000 if f.endswith('.rc'):
996 _rcpath.append(os.path.join(p, f))
1001 _rcpath.append(os.path.join(p, f))
997 else:
1002 else:
998 _rcpath.append(p)
1003 _rcpath.append(p)
999 else:
1004 else:
1000 _rcpath = os_rcpath()
1005 _rcpath = os_rcpath()
1001 return _rcpath
1006 return _rcpath
1002
1007
1003 def bytecount(nbytes):
1008 def bytecount(nbytes):
1004 '''return byte count formatted as readable string, with units'''
1009 '''return byte count formatted as readable string, with units'''
1005
1010
1006 units = (
1011 units = (
1007 (100, 1<<30, _('%.0f GB')),
1012 (100, 1<<30, _('%.0f GB')),
1008 (10, 1<<30, _('%.1f GB')),
1013 (10, 1<<30, _('%.1f GB')),
1009 (1, 1<<30, _('%.2f GB')),
1014 (1, 1<<30, _('%.2f GB')),
1010 (100, 1<<20, _('%.0f MB')),
1015 (100, 1<<20, _('%.0f MB')),
1011 (10, 1<<20, _('%.1f MB')),
1016 (10, 1<<20, _('%.1f MB')),
1012 (1, 1<<20, _('%.2f MB')),
1017 (1, 1<<20, _('%.2f MB')),
1013 (100, 1<<10, _('%.0f KB')),
1018 (100, 1<<10, _('%.0f KB')),
1014 (10, 1<<10, _('%.1f KB')),
1019 (10, 1<<10, _('%.1f KB')),
1015 (1, 1<<10, _('%.2f KB')),
1020 (1, 1<<10, _('%.2f KB')),
1016 (1, 1, _('%.0f bytes')),
1021 (1, 1, _('%.0f bytes')),
1017 )
1022 )
1018
1023
1019 for multiplier, divisor, format in units:
1024 for multiplier, divisor, format in units:
1020 if nbytes >= divisor * multiplier:
1025 if nbytes >= divisor * multiplier:
1021 return format % (nbytes / float(divisor))
1026 return format % (nbytes / float(divisor))
1022 return units[-1][2] % nbytes
1027 return units[-1][2] % nbytes
1023
1028
1024 def drop_scheme(scheme, path):
1029 def drop_scheme(scheme, path):
1025 sc = scheme + ':'
1030 sc = scheme + ':'
1026 if path.startswith(sc):
1031 if path.startswith(sc):
1027 path = path[len(sc):]
1032 path = path[len(sc):]
1028 if path.startswith('//'):
1033 if path.startswith('//'):
1029 path = path[2:]
1034 path = path[2:]
1030 return path
1035 return path
General Comments 0
You need to be logged in to leave comments. Login now