##// END OF EJS Templates
rename util.lookup_reg to lookupreg
Adrian Buehlmann -
r14230:d5163030 default
parent child Browse files
Show More
@@ -1,269 +1,269
1 # filemerge.py - file-level merge handling for Mercurial
1 # filemerge.py - file-level merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import short
8 from node import short
9 from i18n import _
9 from i18n import _
10 import util, simplemerge, match, error
10 import util, simplemerge, match, error
11 import os, tempfile, re, filecmp
11 import os, tempfile, re, filecmp
12
12
13 def _toolstr(ui, tool, part, default=""):
13 def _toolstr(ui, tool, part, default=""):
14 return ui.config("merge-tools", tool + "." + part, default)
14 return ui.config("merge-tools", tool + "." + part, default)
15
15
16 def _toolbool(ui, tool, part, default=False):
16 def _toolbool(ui, tool, part, default=False):
17 return ui.configbool("merge-tools", tool + "." + part, default)
17 return ui.configbool("merge-tools", tool + "." + part, default)
18
18
19 def _toollist(ui, tool, part, default=[]):
19 def _toollist(ui, tool, part, default=[]):
20 return ui.configlist("merge-tools", tool + "." + part, default)
20 return ui.configlist("merge-tools", tool + "." + part, default)
21
21
22 _internal = ['internal:' + s
22 _internal = ['internal:' + s
23 for s in 'fail local other merge prompt dump'.split()]
23 for s in 'fail local other merge prompt dump'.split()]
24
24
25 def _findtool(ui, tool):
25 def _findtool(ui, tool):
26 if tool in _internal:
26 if tool in _internal:
27 return tool
27 return tool
28 for kn in ("regkey", "regkeyalt"):
28 for kn in ("regkey", "regkeyalt"):
29 k = _toolstr(ui, tool, kn)
29 k = _toolstr(ui, tool, kn)
30 if not k:
30 if not k:
31 continue
31 continue
32 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
32 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
33 if p:
33 if p:
34 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
34 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
35 if p:
35 if p:
36 return p
36 return p
37 return util.find_exe(_toolstr(ui, tool, "executable", tool))
37 return util.find_exe(_toolstr(ui, tool, "executable", tool))
38
38
39 def _picktool(repo, ui, path, binary, symlink):
39 def _picktool(repo, ui, path, binary, symlink):
40 def check(tool, pat, symlink, binary):
40 def check(tool, pat, symlink, binary):
41 tmsg = tool
41 tmsg = tool
42 if pat:
42 if pat:
43 tmsg += " specified for " + pat
43 tmsg += " specified for " + pat
44 if not _findtool(ui, tool):
44 if not _findtool(ui, tool):
45 if pat: # explicitly requested tool deserves a warning
45 if pat: # explicitly requested tool deserves a warning
46 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
46 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
47 else: # configured but non-existing tools are more silent
47 else: # configured but non-existing tools are more silent
48 ui.note(_("couldn't find merge tool %s\n") % tmsg)
48 ui.note(_("couldn't find merge tool %s\n") % tmsg)
49 elif symlink and not _toolbool(ui, tool, "symlink"):
49 elif symlink and not _toolbool(ui, tool, "symlink"):
50 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
50 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
51 elif binary and not _toolbool(ui, tool, "binary"):
51 elif binary and not _toolbool(ui, tool, "binary"):
52 ui.warn(_("tool %s can't handle binary\n") % tmsg)
52 ui.warn(_("tool %s can't handle binary\n") % tmsg)
53 elif not util.gui() and _toolbool(ui, tool, "gui"):
53 elif not util.gui() and _toolbool(ui, tool, "gui"):
54 ui.warn(_("tool %s requires a GUI\n") % tmsg)
54 ui.warn(_("tool %s requires a GUI\n") % tmsg)
55 else:
55 else:
56 return True
56 return True
57 return False
57 return False
58
58
59 # forcemerge comes from command line arguments, highest priority
59 # forcemerge comes from command line arguments, highest priority
60 force = ui.config('ui', 'forcemerge')
60 force = ui.config('ui', 'forcemerge')
61 if force:
61 if force:
62 toolpath = _findtool(ui, force)
62 toolpath = _findtool(ui, force)
63 if toolpath:
63 if toolpath:
64 return (force, '"' + toolpath + '"')
64 return (force, '"' + toolpath + '"')
65 else:
65 else:
66 # mimic HGMERGE if given tool not found
66 # mimic HGMERGE if given tool not found
67 return (force, force)
67 return (force, force)
68
68
69 # HGMERGE takes next precedence
69 # HGMERGE takes next precedence
70 hgmerge = os.environ.get("HGMERGE")
70 hgmerge = os.environ.get("HGMERGE")
71 if hgmerge:
71 if hgmerge:
72 return (hgmerge, hgmerge)
72 return (hgmerge, hgmerge)
73
73
74 # then patterns
74 # then patterns
75 for pat, tool in ui.configitems("merge-patterns"):
75 for pat, tool in ui.configitems("merge-patterns"):
76 mf = match.match(repo.root, '', [pat])
76 mf = match.match(repo.root, '', [pat])
77 if mf(path) and check(tool, pat, symlink, False):
77 if mf(path) and check(tool, pat, symlink, False):
78 toolpath = _findtool(ui, tool)
78 toolpath = _findtool(ui, tool)
79 return (tool, '"' + toolpath + '"')
79 return (tool, '"' + toolpath + '"')
80
80
81 # then merge tools
81 # then merge tools
82 tools = {}
82 tools = {}
83 for k, v in ui.configitems("merge-tools"):
83 for k, v in ui.configitems("merge-tools"):
84 t = k.split('.')[0]
84 t = k.split('.')[0]
85 if t not in tools:
85 if t not in tools:
86 tools[t] = int(_toolstr(ui, t, "priority", "0"))
86 tools[t] = int(_toolstr(ui, t, "priority", "0"))
87 names = tools.keys()
87 names = tools.keys()
88 tools = sorted([(-p, t) for t, p in tools.items()])
88 tools = sorted([(-p, t) for t, p in tools.items()])
89 uimerge = ui.config("ui", "merge")
89 uimerge = ui.config("ui", "merge")
90 if uimerge:
90 if uimerge:
91 if uimerge not in names:
91 if uimerge not in names:
92 return (uimerge, uimerge)
92 return (uimerge, uimerge)
93 tools.insert(0, (None, uimerge)) # highest priority
93 tools.insert(0, (None, uimerge)) # highest priority
94 tools.append((None, "hgmerge")) # the old default, if found
94 tools.append((None, "hgmerge")) # the old default, if found
95 for p, t in tools:
95 for p, t in tools:
96 if check(t, None, symlink, binary):
96 if check(t, None, symlink, binary):
97 toolpath = _findtool(ui, t)
97 toolpath = _findtool(ui, t)
98 return (t, '"' + toolpath + '"')
98 return (t, '"' + toolpath + '"')
99 # internal merge as last resort
99 # internal merge as last resort
100 return (not (symlink or binary) and "internal:merge" or None, None)
100 return (not (symlink or binary) and "internal:merge" or None, None)
101
101
102 def _eoltype(data):
102 def _eoltype(data):
103 "Guess the EOL type of a file"
103 "Guess the EOL type of a file"
104 if '\0' in data: # binary
104 if '\0' in data: # binary
105 return None
105 return None
106 if '\r\n' in data: # Windows
106 if '\r\n' in data: # Windows
107 return '\r\n'
107 return '\r\n'
108 if '\r' in data: # Old Mac
108 if '\r' in data: # Old Mac
109 return '\r'
109 return '\r'
110 if '\n' in data: # UNIX
110 if '\n' in data: # UNIX
111 return '\n'
111 return '\n'
112 return None # unknown
112 return None # unknown
113
113
114 def _matcheol(file, origfile):
114 def _matcheol(file, origfile):
115 "Convert EOL markers in a file to match origfile"
115 "Convert EOL markers in a file to match origfile"
116 tostyle = _eoltype(util.readfile(origfile))
116 tostyle = _eoltype(util.readfile(origfile))
117 if tostyle:
117 if tostyle:
118 data = util.readfile(file)
118 data = util.readfile(file)
119 style = _eoltype(data)
119 style = _eoltype(data)
120 if style:
120 if style:
121 newdata = data.replace(style, tostyle)
121 newdata = data.replace(style, tostyle)
122 if newdata != data:
122 if newdata != data:
123 util.writefile(file, newdata)
123 util.writefile(file, newdata)
124
124
125 def filemerge(repo, mynode, orig, fcd, fco, fca):
125 def filemerge(repo, mynode, orig, fcd, fco, fca):
126 """perform a 3-way merge in the working directory
126 """perform a 3-way merge in the working directory
127
127
128 mynode = parent node before merge
128 mynode = parent node before merge
129 orig = original local filename before merge
129 orig = original local filename before merge
130 fco = other file context
130 fco = other file context
131 fca = ancestor file context
131 fca = ancestor file context
132 fcd = local file context for current/destination file
132 fcd = local file context for current/destination file
133 """
133 """
134
134
135 def temp(prefix, ctx):
135 def temp(prefix, ctx):
136 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
136 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
137 (fd, name) = tempfile.mkstemp(prefix=pre)
137 (fd, name) = tempfile.mkstemp(prefix=pre)
138 data = repo.wwritedata(ctx.path(), ctx.data())
138 data = repo.wwritedata(ctx.path(), ctx.data())
139 f = os.fdopen(fd, "wb")
139 f = os.fdopen(fd, "wb")
140 f.write(data)
140 f.write(data)
141 f.close()
141 f.close()
142 return name
142 return name
143
143
144 def isbin(ctx):
144 def isbin(ctx):
145 try:
145 try:
146 return util.binary(ctx.data())
146 return util.binary(ctx.data())
147 except IOError:
147 except IOError:
148 return False
148 return False
149
149
150 if not fco.cmp(fcd): # files identical?
150 if not fco.cmp(fcd): # files identical?
151 return None
151 return None
152
152
153 ui = repo.ui
153 ui = repo.ui
154 fd = fcd.path()
154 fd = fcd.path()
155 binary = isbin(fcd) or isbin(fco) or isbin(fca)
155 binary = isbin(fcd) or isbin(fco) or isbin(fca)
156 symlink = 'l' in fcd.flags() + fco.flags()
156 symlink = 'l' in fcd.flags() + fco.flags()
157 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
157 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
158 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
158 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
159 (tool, fd, binary, symlink))
159 (tool, fd, binary, symlink))
160
160
161 if not tool or tool == 'internal:prompt':
161 if not tool or tool == 'internal:prompt':
162 tool = "internal:local"
162 tool = "internal:local"
163 if ui.promptchoice(_(" no tool found to merge %s\n"
163 if ui.promptchoice(_(" no tool found to merge %s\n"
164 "keep (l)ocal or take (o)ther?") % fd,
164 "keep (l)ocal or take (o)ther?") % fd,
165 (_("&Local"), _("&Other")), 0):
165 (_("&Local"), _("&Other")), 0):
166 tool = "internal:other"
166 tool = "internal:other"
167 if tool == "internal:local":
167 if tool == "internal:local":
168 return 0
168 return 0
169 if tool == "internal:other":
169 if tool == "internal:other":
170 repo.wwrite(fd, fco.data(), fco.flags())
170 repo.wwrite(fd, fco.data(), fco.flags())
171 return 0
171 return 0
172 if tool == "internal:fail":
172 if tool == "internal:fail":
173 return 1
173 return 1
174
174
175 # do the actual merge
175 # do the actual merge
176 a = repo.wjoin(fd)
176 a = repo.wjoin(fd)
177 b = temp("base", fca)
177 b = temp("base", fca)
178 c = temp("other", fco)
178 c = temp("other", fco)
179 out = ""
179 out = ""
180 back = a + ".orig"
180 back = a + ".orig"
181 util.copyfile(a, back)
181 util.copyfile(a, back)
182
182
183 if orig != fco.path():
183 if orig != fco.path():
184 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
184 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
185 else:
185 else:
186 ui.status(_("merging %s\n") % fd)
186 ui.status(_("merging %s\n") % fd)
187
187
188 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
188 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
189
189
190 # do we attempt to simplemerge first?
190 # do we attempt to simplemerge first?
191 try:
191 try:
192 premerge = _toolbool(ui, tool, "premerge", not (binary or symlink))
192 premerge = _toolbool(ui, tool, "premerge", not (binary or symlink))
193 except error.ConfigError:
193 except error.ConfigError:
194 premerge = _toolstr(ui, tool, "premerge").lower()
194 premerge = _toolstr(ui, tool, "premerge").lower()
195 valid = 'keep'.split()
195 valid = 'keep'.split()
196 if premerge not in valid:
196 if premerge not in valid:
197 _valid = ', '.join(["'" + v + "'" for v in valid])
197 _valid = ', '.join(["'" + v + "'" for v in valid])
198 raise error.ConfigError(_("%s.premerge not valid "
198 raise error.ConfigError(_("%s.premerge not valid "
199 "('%s' is neither boolean nor %s)") %
199 "('%s' is neither boolean nor %s)") %
200 (tool, premerge, _valid))
200 (tool, premerge, _valid))
201
201
202 if premerge:
202 if premerge:
203 r = simplemerge.simplemerge(ui, a, b, c, quiet=True)
203 r = simplemerge.simplemerge(ui, a, b, c, quiet=True)
204 if not r:
204 if not r:
205 ui.debug(" premerge successful\n")
205 ui.debug(" premerge successful\n")
206 os.unlink(back)
206 os.unlink(back)
207 os.unlink(b)
207 os.unlink(b)
208 os.unlink(c)
208 os.unlink(c)
209 return 0
209 return 0
210 if premerge != 'keep':
210 if premerge != 'keep':
211 util.copyfile(back, a) # restore from backup and try again
211 util.copyfile(back, a) # restore from backup and try again
212
212
213 env = dict(HG_FILE=fd,
213 env = dict(HG_FILE=fd,
214 HG_MY_NODE=short(mynode),
214 HG_MY_NODE=short(mynode),
215 HG_OTHER_NODE=str(fco.changectx()),
215 HG_OTHER_NODE=str(fco.changectx()),
216 HG_BASE_NODE=str(fca.changectx()),
216 HG_BASE_NODE=str(fca.changectx()),
217 HG_MY_ISLINK='l' in fcd.flags(),
217 HG_MY_ISLINK='l' in fcd.flags(),
218 HG_OTHER_ISLINK='l' in fco.flags(),
218 HG_OTHER_ISLINK='l' in fco.flags(),
219 HG_BASE_ISLINK='l' in fca.flags())
219 HG_BASE_ISLINK='l' in fca.flags())
220
220
221 if tool == "internal:merge":
221 if tool == "internal:merge":
222 r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other'])
222 r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other'])
223 elif tool == 'internal:dump':
223 elif tool == 'internal:dump':
224 a = repo.wjoin(fd)
224 a = repo.wjoin(fd)
225 util.copyfile(a, a + ".local")
225 util.copyfile(a, a + ".local")
226 repo.wwrite(fd + ".other", fco.data(), fco.flags())
226 repo.wwrite(fd + ".other", fco.data(), fco.flags())
227 repo.wwrite(fd + ".base", fca.data(), fca.flags())
227 repo.wwrite(fd + ".base", fca.data(), fca.flags())
228 return 1 # unresolved
228 return 1 # unresolved
229 else:
229 else:
230 args = _toolstr(ui, tool, "args", '$local $base $other')
230 args = _toolstr(ui, tool, "args", '$local $base $other')
231 if "$output" in args:
231 if "$output" in args:
232 out, a = a, back # read input from backup, write to original
232 out, a = a, back # read input from backup, write to original
233 replace = dict(local=a, base=b, other=c, output=out)
233 replace = dict(local=a, base=b, other=c, output=out)
234 args = util.interpolate(r'\$', replace, args,
234 args = util.interpolate(r'\$', replace, args,
235 lambda s: '"%s"' % util.localpath(s))
235 lambda s: '"%s"' % util.localpath(s))
236 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
236 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
237
237
238 if not r and (_toolbool(ui, tool, "checkconflicts") or
238 if not r and (_toolbool(ui, tool, "checkconflicts") or
239 'conflicts' in _toollist(ui, tool, "check")):
239 'conflicts' in _toollist(ui, tool, "check")):
240 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
240 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
241 re.MULTILINE):
241 re.MULTILINE):
242 r = 1
242 r = 1
243
243
244 checked = False
244 checked = False
245 if 'prompt' in _toollist(ui, tool, "check"):
245 if 'prompt' in _toollist(ui, tool, "check"):
246 checked = True
246 checked = True
247 if ui.promptchoice(_("was merge of '%s' successful (yn)?") % fd,
247 if ui.promptchoice(_("was merge of '%s' successful (yn)?") % fd,
248 (_("&Yes"), _("&No")), 1):
248 (_("&Yes"), _("&No")), 1):
249 r = 1
249 r = 1
250
250
251 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
251 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
252 'changed' in _toollist(ui, tool, "check")):
252 'changed' in _toollist(ui, tool, "check")):
253 if filecmp.cmp(repo.wjoin(fd), back):
253 if filecmp.cmp(repo.wjoin(fd), back):
254 if ui.promptchoice(_(" output file %s appears unchanged\n"
254 if ui.promptchoice(_(" output file %s appears unchanged\n"
255 "was merge successful (yn)?") % fd,
255 "was merge successful (yn)?") % fd,
256 (_("&Yes"), _("&No")), 1):
256 (_("&Yes"), _("&No")), 1):
257 r = 1
257 r = 1
258
258
259 if _toolbool(ui, tool, "fixeol"):
259 if _toolbool(ui, tool, "fixeol"):
260 _matcheol(repo.wjoin(fd), back)
260 _matcheol(repo.wjoin(fd), back)
261
261
262 if r:
262 if r:
263 ui.warn(_("merging %s failed!\n") % fd)
263 ui.warn(_("merging %s failed!\n") % fd)
264 else:
264 else:
265 os.unlink(back)
265 os.unlink(back)
266
266
267 os.unlink(b)
267 os.unlink(b)
268 os.unlink(c)
268 os.unlink(c)
269 return r
269 return r
@@ -1,465 +1,465
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import util, error, osutil
9 import util, error, osutil
10 import os, errno, stat, sys
10 import os, errno, stat, sys
11
11
12 def checkfilename(f):
12 def checkfilename(f):
13 '''Check that the filename f is an acceptable filename for a tracked file'''
13 '''Check that the filename f is an acceptable filename for a tracked file'''
14 if '\r' in f or '\n' in f:
14 if '\r' in f or '\n' in f:
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16
16
17 def checkportable(ui, f):
17 def checkportable(ui, f):
18 '''Check if filename f is portable and warn or abort depending on config'''
18 '''Check if filename f is portable and warn or abort depending on config'''
19 checkfilename(f)
19 checkfilename(f)
20 abort, warn = checkportabilityalert(ui)
20 abort, warn = checkportabilityalert(ui)
21 if abort or warn:
21 if abort or warn:
22 msg = util.checkwinfilename(f)
22 msg = util.checkwinfilename(f)
23 if msg:
23 if msg:
24 msg = "%s: %r" % (msg, f)
24 msg = "%s: %r" % (msg, f)
25 if abort:
25 if abort:
26 raise util.Abort(msg)
26 raise util.Abort(msg)
27 ui.warn(_("warning: %s\n") % msg)
27 ui.warn(_("warning: %s\n") % msg)
28
28
29 def checkportabilityalert(ui):
29 def checkportabilityalert(ui):
30 '''check if the user's config requests nothing, a warning, or abort for
30 '''check if the user's config requests nothing, a warning, or abort for
31 non-portable filenames'''
31 non-portable filenames'''
32 val = ui.config('ui', 'portablefilenames', 'warn')
32 val = ui.config('ui', 'portablefilenames', 'warn')
33 lval = val.lower()
33 lval = val.lower()
34 bval = util.parsebool(val)
34 bval = util.parsebool(val)
35 abort = os.name == 'nt' or lval == 'abort'
35 abort = os.name == 'nt' or lval == 'abort'
36 warn = bval or lval == 'warn'
36 warn = bval or lval == 'warn'
37 if bval is None and not (warn or abort or lval == 'ignore'):
37 if bval is None and not (warn or abort or lval == 'ignore'):
38 raise error.ConfigError(
38 raise error.ConfigError(
39 _("ui.portablefilenames value is invalid ('%s')") % val)
39 _("ui.portablefilenames value is invalid ('%s')") % val)
40 return abort, warn
40 return abort, warn
41
41
42 class casecollisionauditor(object):
42 class casecollisionauditor(object):
43 def __init__(self, ui, abort, existingiter):
43 def __init__(self, ui, abort, existingiter):
44 self._ui = ui
44 self._ui = ui
45 self._abort = abort
45 self._abort = abort
46 self._map = {}
46 self._map = {}
47 for f in existingiter:
47 for f in existingiter:
48 self._map[f.lower()] = f
48 self._map[f.lower()] = f
49
49
50 def __call__(self, f):
50 def __call__(self, f):
51 fl = f.lower()
51 fl = f.lower()
52 map = self._map
52 map = self._map
53 if fl in map and map[fl] != f:
53 if fl in map and map[fl] != f:
54 msg = _('possible case-folding collision for %s') % f
54 msg = _('possible case-folding collision for %s') % f
55 if self._abort:
55 if self._abort:
56 raise util.Abort(msg)
56 raise util.Abort(msg)
57 self._ui.warn(_("warning: %s\n") % msg)
57 self._ui.warn(_("warning: %s\n") % msg)
58 map[fl] = f
58 map[fl] = f
59
59
60 class pathauditor(object):
60 class pathauditor(object):
61 '''ensure that a filesystem path contains no banned components.
61 '''ensure that a filesystem path contains no banned components.
62 the following properties of a path are checked:
62 the following properties of a path are checked:
63
63
64 - ends with a directory separator
64 - ends with a directory separator
65 - under top-level .hg
65 - under top-level .hg
66 - starts at the root of a windows drive
66 - starts at the root of a windows drive
67 - contains ".."
67 - contains ".."
68 - traverses a symlink (e.g. a/symlink_here/b)
68 - traverses a symlink (e.g. a/symlink_here/b)
69 - inside a nested repository (a callback can be used to approve
69 - inside a nested repository (a callback can be used to approve
70 some nested repositories, e.g., subrepositories)
70 some nested repositories, e.g., subrepositories)
71 '''
71 '''
72
72
73 def __init__(self, root, callback=None):
73 def __init__(self, root, callback=None):
74 self.audited = set()
74 self.audited = set()
75 self.auditeddir = set()
75 self.auditeddir = set()
76 self.root = root
76 self.root = root
77 self.callback = callback
77 self.callback = callback
78
78
79 def __call__(self, path):
79 def __call__(self, path):
80 '''Check the relative path.
80 '''Check the relative path.
81 path may contain a pattern (e.g. foodir/**.txt)'''
81 path may contain a pattern (e.g. foodir/**.txt)'''
82
82
83 if path in self.audited:
83 if path in self.audited:
84 return
84 return
85 # AIX ignores "/" at end of path, others raise EISDIR.
85 # AIX ignores "/" at end of path, others raise EISDIR.
86 if util.endswithsep(path):
86 if util.endswithsep(path):
87 raise util.Abort(_("path ends in directory separator: %s") % path)
87 raise util.Abort(_("path ends in directory separator: %s") % path)
88 normpath = os.path.normcase(path)
88 normpath = os.path.normcase(path)
89 parts = util.splitpath(normpath)
89 parts = util.splitpath(normpath)
90 if (os.path.splitdrive(path)[0]
90 if (os.path.splitdrive(path)[0]
91 or parts[0].lower() in ('.hg', '.hg.', '')
91 or parts[0].lower() in ('.hg', '.hg.', '')
92 or os.pardir in parts):
92 or os.pardir in parts):
93 raise util.Abort(_("path contains illegal component: %s") % path)
93 raise util.Abort(_("path contains illegal component: %s") % path)
94 if '.hg' in path.lower():
94 if '.hg' in path.lower():
95 lparts = [p.lower() for p in parts]
95 lparts = [p.lower() for p in parts]
96 for p in '.hg', '.hg.':
96 for p in '.hg', '.hg.':
97 if p in lparts[1:]:
97 if p in lparts[1:]:
98 pos = lparts.index(p)
98 pos = lparts.index(p)
99 base = os.path.join(*parts[:pos])
99 base = os.path.join(*parts[:pos])
100 raise util.Abort(_('path %r is inside nested repo %r')
100 raise util.Abort(_('path %r is inside nested repo %r')
101 % (path, base))
101 % (path, base))
102
102
103 parts.pop()
103 parts.pop()
104 prefixes = []
104 prefixes = []
105 while parts:
105 while parts:
106 prefix = os.sep.join(parts)
106 prefix = os.sep.join(parts)
107 if prefix in self.auditeddir:
107 if prefix in self.auditeddir:
108 break
108 break
109 curpath = os.path.join(self.root, prefix)
109 curpath = os.path.join(self.root, prefix)
110 try:
110 try:
111 st = os.lstat(curpath)
111 st = os.lstat(curpath)
112 except OSError, err:
112 except OSError, err:
113 # EINVAL can be raised as invalid path syntax under win32.
113 # EINVAL can be raised as invalid path syntax under win32.
114 # They must be ignored for patterns can be checked too.
114 # They must be ignored for patterns can be checked too.
115 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
115 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
116 raise
116 raise
117 else:
117 else:
118 if stat.S_ISLNK(st.st_mode):
118 if stat.S_ISLNK(st.st_mode):
119 raise util.Abort(
119 raise util.Abort(
120 _('path %r traverses symbolic link %r')
120 _('path %r traverses symbolic link %r')
121 % (path, prefix))
121 % (path, prefix))
122 elif (stat.S_ISDIR(st.st_mode) and
122 elif (stat.S_ISDIR(st.st_mode) and
123 os.path.isdir(os.path.join(curpath, '.hg'))):
123 os.path.isdir(os.path.join(curpath, '.hg'))):
124 if not self.callback or not self.callback(curpath):
124 if not self.callback or not self.callback(curpath):
125 raise util.Abort(_('path %r is inside nested repo %r') %
125 raise util.Abort(_('path %r is inside nested repo %r') %
126 (path, prefix))
126 (path, prefix))
127 prefixes.append(prefix)
127 prefixes.append(prefix)
128 parts.pop()
128 parts.pop()
129
129
130 self.audited.add(path)
130 self.audited.add(path)
131 # only add prefixes to the cache after checking everything: we don't
131 # only add prefixes to the cache after checking everything: we don't
132 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
132 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
133 self.auditeddir.update(prefixes)
133 self.auditeddir.update(prefixes)
134
134
135 class abstractopener(object):
135 class abstractopener(object):
136 """Abstract base class; cannot be instantiated"""
136 """Abstract base class; cannot be instantiated"""
137
137
138 def __init__(self, *args, **kwargs):
138 def __init__(self, *args, **kwargs):
139 '''Prevent instantiation; don't call this from subclasses.'''
139 '''Prevent instantiation; don't call this from subclasses.'''
140 raise NotImplementedError('attempted instantiating ' + str(type(self)))
140 raise NotImplementedError('attempted instantiating ' + str(type(self)))
141
141
142 def read(self, path):
142 def read(self, path):
143 fp = self(path, 'rb')
143 fp = self(path, 'rb')
144 try:
144 try:
145 return fp.read()
145 return fp.read()
146 finally:
146 finally:
147 fp.close()
147 fp.close()
148
148
149 def write(self, path, data):
149 def write(self, path, data):
150 fp = self(path, 'wb')
150 fp = self(path, 'wb')
151 try:
151 try:
152 return fp.write(data)
152 return fp.write(data)
153 finally:
153 finally:
154 fp.close()
154 fp.close()
155
155
156 def append(self, path, data):
156 def append(self, path, data):
157 fp = self(path, 'ab')
157 fp = self(path, 'ab')
158 try:
158 try:
159 return fp.write(data)
159 return fp.write(data)
160 finally:
160 finally:
161 fp.close()
161 fp.close()
162
162
163 class opener(abstractopener):
163 class opener(abstractopener):
164 '''Open files relative to a base directory
164 '''Open files relative to a base directory
165
165
166 This class is used to hide the details of COW semantics and
166 This class is used to hide the details of COW semantics and
167 remote file access from higher level code.
167 remote file access from higher level code.
168 '''
168 '''
169 def __init__(self, base, audit=True):
169 def __init__(self, base, audit=True):
170 self.base = base
170 self.base = base
171 if audit:
171 if audit:
172 self.auditor = pathauditor(base)
172 self.auditor = pathauditor(base)
173 else:
173 else:
174 self.auditor = util.always
174 self.auditor = util.always
175 self.createmode = None
175 self.createmode = None
176 self._trustnlink = None
176 self._trustnlink = None
177
177
178 @util.propertycache
178 @util.propertycache
179 def _can_symlink(self):
179 def _can_symlink(self):
180 return util.checklink(self.base)
180 return util.checklink(self.base)
181
181
182 def _fixfilemode(self, name):
182 def _fixfilemode(self, name):
183 if self.createmode is None:
183 if self.createmode is None:
184 return
184 return
185 os.chmod(name, self.createmode & 0666)
185 os.chmod(name, self.createmode & 0666)
186
186
187 def __call__(self, path, mode="r", text=False, atomictemp=False):
187 def __call__(self, path, mode="r", text=False, atomictemp=False):
188 r = util.checkosfilename(path)
188 r = util.checkosfilename(path)
189 if r:
189 if r:
190 raise util.Abort("%s: %r" % (r, path))
190 raise util.Abort("%s: %r" % (r, path))
191 self.auditor(path)
191 self.auditor(path)
192 f = os.path.join(self.base, path)
192 f = os.path.join(self.base, path)
193
193
194 if not text and "b" not in mode:
194 if not text and "b" not in mode:
195 mode += "b" # for that other OS
195 mode += "b" # for that other OS
196
196
197 nlink = -1
197 nlink = -1
198 dirname, basename = os.path.split(f)
198 dirname, basename = os.path.split(f)
199 # If basename is empty, then the path is malformed because it points
199 # If basename is empty, then the path is malformed because it points
200 # to a directory. Let the posixfile() call below raise IOError.
200 # to a directory. Let the posixfile() call below raise IOError.
201 if basename and mode not in ('r', 'rb'):
201 if basename and mode not in ('r', 'rb'):
202 if atomictemp:
202 if atomictemp:
203 if not os.path.isdir(dirname):
203 if not os.path.isdir(dirname):
204 util.makedirs(dirname, self.createmode)
204 util.makedirs(dirname, self.createmode)
205 return util.atomictempfile(f, mode, self.createmode)
205 return util.atomictempfile(f, mode, self.createmode)
206 try:
206 try:
207 if 'w' in mode:
207 if 'w' in mode:
208 util.unlink(f)
208 util.unlink(f)
209 nlink = 0
209 nlink = 0
210 else:
210 else:
211 # nlinks() may behave differently for files on Windows
211 # nlinks() may behave differently for files on Windows
212 # shares if the file is open.
212 # shares if the file is open.
213 fd = util.posixfile(f)
213 fd = util.posixfile(f)
214 nlink = util.nlinks(f)
214 nlink = util.nlinks(f)
215 if nlink < 1:
215 if nlink < 1:
216 nlink = 2 # force mktempcopy (issue1922)
216 nlink = 2 # force mktempcopy (issue1922)
217 fd.close()
217 fd.close()
218 except (OSError, IOError), e:
218 except (OSError, IOError), e:
219 if e.errno != errno.ENOENT:
219 if e.errno != errno.ENOENT:
220 raise
220 raise
221 nlink = 0
221 nlink = 0
222 if not os.path.isdir(dirname):
222 if not os.path.isdir(dirname):
223 util.makedirs(dirname, self.createmode)
223 util.makedirs(dirname, self.createmode)
224 if nlink > 0:
224 if nlink > 0:
225 if self._trustnlink is None:
225 if self._trustnlink is None:
226 self._trustnlink = nlink > 1 or util.checknlink(f)
226 self._trustnlink = nlink > 1 or util.checknlink(f)
227 if nlink > 1 or not self._trustnlink:
227 if nlink > 1 or not self._trustnlink:
228 util.rename(util.mktempcopy(f), f)
228 util.rename(util.mktempcopy(f), f)
229 fp = util.posixfile(f, mode)
229 fp = util.posixfile(f, mode)
230 if nlink == 0:
230 if nlink == 0:
231 self._fixfilemode(f)
231 self._fixfilemode(f)
232 return fp
232 return fp
233
233
234 def symlink(self, src, dst):
234 def symlink(self, src, dst):
235 self.auditor(dst)
235 self.auditor(dst)
236 linkname = os.path.join(self.base, dst)
236 linkname = os.path.join(self.base, dst)
237 try:
237 try:
238 os.unlink(linkname)
238 os.unlink(linkname)
239 except OSError:
239 except OSError:
240 pass
240 pass
241
241
242 dirname = os.path.dirname(linkname)
242 dirname = os.path.dirname(linkname)
243 if not os.path.exists(dirname):
243 if not os.path.exists(dirname):
244 util.makedirs(dirname, self.createmode)
244 util.makedirs(dirname, self.createmode)
245
245
246 if self._can_symlink:
246 if self._can_symlink:
247 try:
247 try:
248 os.symlink(src, linkname)
248 os.symlink(src, linkname)
249 except OSError, err:
249 except OSError, err:
250 raise OSError(err.errno, _('could not symlink to %r: %s') %
250 raise OSError(err.errno, _('could not symlink to %r: %s') %
251 (src, err.strerror), linkname)
251 (src, err.strerror), linkname)
252 else:
252 else:
253 f = self(dst, "w")
253 f = self(dst, "w")
254 f.write(src)
254 f.write(src)
255 f.close()
255 f.close()
256 self._fixfilemode(dst)
256 self._fixfilemode(dst)
257
257
258 class filteropener(abstractopener):
258 class filteropener(abstractopener):
259 '''Wrapper opener for filtering filenames with a function.'''
259 '''Wrapper opener for filtering filenames with a function.'''
260
260
261 def __init__(self, opener, filter):
261 def __init__(self, opener, filter):
262 self._filter = filter
262 self._filter = filter
263 self._orig = opener
263 self._orig = opener
264
264
265 def __call__(self, path, *args, **kwargs):
265 def __call__(self, path, *args, **kwargs):
266 return self._orig(self._filter(path), *args, **kwargs)
266 return self._orig(self._filter(path), *args, **kwargs)
267
267
268 def canonpath(root, cwd, myname, auditor=None):
268 def canonpath(root, cwd, myname, auditor=None):
269 '''return the canonical path of myname, given cwd and root'''
269 '''return the canonical path of myname, given cwd and root'''
270 if util.endswithsep(root):
270 if util.endswithsep(root):
271 rootsep = root
271 rootsep = root
272 else:
272 else:
273 rootsep = root + os.sep
273 rootsep = root + os.sep
274 name = myname
274 name = myname
275 if not os.path.isabs(name):
275 if not os.path.isabs(name):
276 name = os.path.join(root, cwd, name)
276 name = os.path.join(root, cwd, name)
277 name = os.path.normpath(name)
277 name = os.path.normpath(name)
278 if auditor is None:
278 if auditor is None:
279 auditor = pathauditor(root)
279 auditor = pathauditor(root)
280 if name != rootsep and name.startswith(rootsep):
280 if name != rootsep and name.startswith(rootsep):
281 name = name[len(rootsep):]
281 name = name[len(rootsep):]
282 auditor(name)
282 auditor(name)
283 return util.pconvert(name)
283 return util.pconvert(name)
284 elif name == root:
284 elif name == root:
285 return ''
285 return ''
286 else:
286 else:
287 # Determine whether `name' is in the hierarchy at or beneath `root',
287 # Determine whether `name' is in the hierarchy at or beneath `root',
288 # by iterating name=dirname(name) until that causes no change (can't
288 # by iterating name=dirname(name) until that causes no change (can't
289 # check name == '/', because that doesn't work on windows). For each
289 # check name == '/', because that doesn't work on windows). For each
290 # `name', compare dev/inode numbers. If they match, the list `rel'
290 # `name', compare dev/inode numbers. If they match, the list `rel'
291 # holds the reversed list of components making up the relative file
291 # holds the reversed list of components making up the relative file
292 # name we want.
292 # name we want.
293 root_st = os.stat(root)
293 root_st = os.stat(root)
294 rel = []
294 rel = []
295 while True:
295 while True:
296 try:
296 try:
297 name_st = os.stat(name)
297 name_st = os.stat(name)
298 except OSError:
298 except OSError:
299 break
299 break
300 if util.samestat(name_st, root_st):
300 if util.samestat(name_st, root_st):
301 if not rel:
301 if not rel:
302 # name was actually the same as root (maybe a symlink)
302 # name was actually the same as root (maybe a symlink)
303 return ''
303 return ''
304 rel.reverse()
304 rel.reverse()
305 name = os.path.join(*rel)
305 name = os.path.join(*rel)
306 auditor(name)
306 auditor(name)
307 return util.pconvert(name)
307 return util.pconvert(name)
308 dirname, basename = os.path.split(name)
308 dirname, basename = os.path.split(name)
309 rel.append(basename)
309 rel.append(basename)
310 if dirname == name:
310 if dirname == name:
311 break
311 break
312 name = dirname
312 name = dirname
313
313
314 raise util.Abort('%s not under root' % myname)
314 raise util.Abort('%s not under root' % myname)
315
315
316 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
316 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
317 '''yield every hg repository under path, recursively.'''
317 '''yield every hg repository under path, recursively.'''
318 def errhandler(err):
318 def errhandler(err):
319 if err.filename == path:
319 if err.filename == path:
320 raise err
320 raise err
321 if followsym and hasattr(os.path, 'samestat'):
321 if followsym and hasattr(os.path, 'samestat'):
322 def adddir(dirlst, dirname):
322 def adddir(dirlst, dirname):
323 match = False
323 match = False
324 samestat = os.path.samestat
324 samestat = os.path.samestat
325 dirstat = os.stat(dirname)
325 dirstat = os.stat(dirname)
326 for lstdirstat in dirlst:
326 for lstdirstat in dirlst:
327 if samestat(dirstat, lstdirstat):
327 if samestat(dirstat, lstdirstat):
328 match = True
328 match = True
329 break
329 break
330 if not match:
330 if not match:
331 dirlst.append(dirstat)
331 dirlst.append(dirstat)
332 return not match
332 return not match
333 else:
333 else:
334 followsym = False
334 followsym = False
335
335
336 if (seen_dirs is None) and followsym:
336 if (seen_dirs is None) and followsym:
337 seen_dirs = []
337 seen_dirs = []
338 adddir(seen_dirs, path)
338 adddir(seen_dirs, path)
339 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
339 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
340 dirs.sort()
340 dirs.sort()
341 if '.hg' in dirs:
341 if '.hg' in dirs:
342 yield root # found a repository
342 yield root # found a repository
343 qroot = os.path.join(root, '.hg', 'patches')
343 qroot = os.path.join(root, '.hg', 'patches')
344 if os.path.isdir(os.path.join(qroot, '.hg')):
344 if os.path.isdir(os.path.join(qroot, '.hg')):
345 yield qroot # we have a patch queue repo here
345 yield qroot # we have a patch queue repo here
346 if recurse:
346 if recurse:
347 # avoid recursing inside the .hg directory
347 # avoid recursing inside the .hg directory
348 dirs.remove('.hg')
348 dirs.remove('.hg')
349 else:
349 else:
350 dirs[:] = [] # don't descend further
350 dirs[:] = [] # don't descend further
351 elif followsym:
351 elif followsym:
352 newdirs = []
352 newdirs = []
353 for d in dirs:
353 for d in dirs:
354 fname = os.path.join(root, d)
354 fname = os.path.join(root, d)
355 if adddir(seen_dirs, fname):
355 if adddir(seen_dirs, fname):
356 if os.path.islink(fname):
356 if os.path.islink(fname):
357 for hgname in walkrepos(fname, True, seen_dirs):
357 for hgname in walkrepos(fname, True, seen_dirs):
358 yield hgname
358 yield hgname
359 else:
359 else:
360 newdirs.append(d)
360 newdirs.append(d)
361 dirs[:] = newdirs
361 dirs[:] = newdirs
362
362
363 def osrcpath():
363 def osrcpath():
364 '''return default os-specific hgrc search path'''
364 '''return default os-specific hgrc search path'''
365 path = systemrcpath()
365 path = systemrcpath()
366 path.extend(userrcpath())
366 path.extend(userrcpath())
367 path = [os.path.normpath(f) for f in path]
367 path = [os.path.normpath(f) for f in path]
368 return path
368 return path
369
369
370 _rcpath = None
370 _rcpath = None
371
371
372 def rcpath():
372 def rcpath():
373 '''return hgrc search path. if env var HGRCPATH is set, use it.
373 '''return hgrc search path. if env var HGRCPATH is set, use it.
374 for each item in path, if directory, use files ending in .rc,
374 for each item in path, if directory, use files ending in .rc,
375 else use item.
375 else use item.
376 make HGRCPATH empty to only look in .hg/hgrc of current repo.
376 make HGRCPATH empty to only look in .hg/hgrc of current repo.
377 if no HGRCPATH, use default os-specific path.'''
377 if no HGRCPATH, use default os-specific path.'''
378 global _rcpath
378 global _rcpath
379 if _rcpath is None:
379 if _rcpath is None:
380 if 'HGRCPATH' in os.environ:
380 if 'HGRCPATH' in os.environ:
381 _rcpath = []
381 _rcpath = []
382 for p in os.environ['HGRCPATH'].split(os.pathsep):
382 for p in os.environ['HGRCPATH'].split(os.pathsep):
383 if not p:
383 if not p:
384 continue
384 continue
385 p = util.expandpath(p)
385 p = util.expandpath(p)
386 if os.path.isdir(p):
386 if os.path.isdir(p):
387 for f, kind in osutil.listdir(p):
387 for f, kind in osutil.listdir(p):
388 if f.endswith('.rc'):
388 if f.endswith('.rc'):
389 _rcpath.append(os.path.join(p, f))
389 _rcpath.append(os.path.join(p, f))
390 else:
390 else:
391 _rcpath.append(p)
391 _rcpath.append(p)
392 else:
392 else:
393 _rcpath = osrcpath()
393 _rcpath = osrcpath()
394 return _rcpath
394 return _rcpath
395
395
396 if os.name != 'nt':
396 if os.name != 'nt':
397
397
398 def rcfiles(path):
398 def rcfiles(path):
399 rcs = [os.path.join(path, 'hgrc')]
399 rcs = [os.path.join(path, 'hgrc')]
400 rcdir = os.path.join(path, 'hgrc.d')
400 rcdir = os.path.join(path, 'hgrc.d')
401 try:
401 try:
402 rcs.extend([os.path.join(rcdir, f)
402 rcs.extend([os.path.join(rcdir, f)
403 for f, kind in osutil.listdir(rcdir)
403 for f, kind in osutil.listdir(rcdir)
404 if f.endswith(".rc")])
404 if f.endswith(".rc")])
405 except OSError:
405 except OSError:
406 pass
406 pass
407 return rcs
407 return rcs
408
408
409 def systemrcpath():
409 def systemrcpath():
410 path = []
410 path = []
411 # old mod_python does not set sys.argv
411 # old mod_python does not set sys.argv
412 if len(getattr(sys, 'argv', [])) > 0:
412 if len(getattr(sys, 'argv', [])) > 0:
413 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
413 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
414 '/../etc/mercurial'))
414 '/../etc/mercurial'))
415 path.extend(rcfiles('/etc/mercurial'))
415 path.extend(rcfiles('/etc/mercurial'))
416 return path
416 return path
417
417
418 def userrcpath():
418 def userrcpath():
419 return [os.path.expanduser('~/.hgrc')]
419 return [os.path.expanduser('~/.hgrc')]
420
420
421 else:
421 else:
422
422
423 _HKEY_LOCAL_MACHINE = 0x80000002L
423 _HKEY_LOCAL_MACHINE = 0x80000002L
424
424
425 def systemrcpath():
425 def systemrcpath():
426 '''return default os-specific hgrc search path'''
426 '''return default os-specific hgrc search path'''
427 rcpath = []
427 rcpath = []
428 filename = util.executable_path()
428 filename = util.executable_path()
429 # Use mercurial.ini found in directory with hg.exe
429 # Use mercurial.ini found in directory with hg.exe
430 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
430 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
431 if os.path.isfile(progrc):
431 if os.path.isfile(progrc):
432 rcpath.append(progrc)
432 rcpath.append(progrc)
433 return rcpath
433 return rcpath
434 # Use hgrc.d found in directory with hg.exe
434 # Use hgrc.d found in directory with hg.exe
435 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
435 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
436 if os.path.isdir(progrcd):
436 if os.path.isdir(progrcd):
437 for f, kind in osutil.listdir(progrcd):
437 for f, kind in osutil.listdir(progrcd):
438 if f.endswith('.rc'):
438 if f.endswith('.rc'):
439 rcpath.append(os.path.join(progrcd, f))
439 rcpath.append(os.path.join(progrcd, f))
440 return rcpath
440 return rcpath
441 # else look for a system rcpath in the registry
441 # else look for a system rcpath in the registry
442 value = util.lookup_reg('SOFTWARE\\Mercurial', None,
442 value = util.lookupreg('SOFTWARE\\Mercurial', None,
443 _HKEY_LOCAL_MACHINE)
443 _HKEY_LOCAL_MACHINE)
444 if not isinstance(value, str) or not value:
444 if not isinstance(value, str) or not value:
445 return rcpath
445 return rcpath
446 value = value.replace('/', os.sep)
446 value = value.replace('/', os.sep)
447 for p in value.split(os.pathsep):
447 for p in value.split(os.pathsep):
448 if p.lower().endswith('mercurial.ini'):
448 if p.lower().endswith('mercurial.ini'):
449 rcpath.append(p)
449 rcpath.append(p)
450 elif os.path.isdir(p):
450 elif os.path.isdir(p):
451 for f, kind in osutil.listdir(p):
451 for f, kind in osutil.listdir(p):
452 if f.endswith('.rc'):
452 if f.endswith('.rc'):
453 rcpath.append(os.path.join(p, f))
453 rcpath.append(os.path.join(p, f))
454 return rcpath
454 return rcpath
455
455
456 def userrcpath():
456 def userrcpath():
457 '''return os-specific hgrc search path to the user dir'''
457 '''return os-specific hgrc search path to the user dir'''
458 home = os.path.expanduser('~')
458 home = os.path.expanduser('~')
459 path = [os.path.join(home, 'mercurial.ini'),
459 path = [os.path.join(home, 'mercurial.ini'),
460 os.path.join(home, '.hgrc')]
460 os.path.join(home, '.hgrc')]
461 userprofile = os.environ.get('USERPROFILE')
461 userprofile = os.environ.get('USERPROFILE')
462 if userprofile:
462 if userprofile:
463 path.append(os.path.join(userprofile, 'mercurial.ini'))
463 path.append(os.path.join(userprofile, 'mercurial.ini'))
464 path.append(os.path.join(userprofile, '.hgrc'))
464 path.append(os.path.join(userprofile, '.hgrc'))
465 return path
465 return path
@@ -1,1590 +1,1590
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, unicodedata, signal
19 import os, time, calendar, textwrap, unicodedata, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
31 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 else:
33 else:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explain_exit(code)))
200 (cmd, explain_exit(code)))
201 fp = open(outname, 'rb')
201 fp = open(outname, 'rb')
202 r = fp.read()
202 r = fp.read()
203 fp.close()
203 fp.close()
204 return r
204 return r
205 finally:
205 finally:
206 try:
206 try:
207 if inname:
207 if inname:
208 os.unlink(inname)
208 os.unlink(inname)
209 except OSError:
209 except OSError:
210 pass
210 pass
211 try:
211 try:
212 if outname:
212 if outname:
213 os.unlink(outname)
213 os.unlink(outname)
214 except OSError:
214 except OSError:
215 pass
215 pass
216
216
217 filtertable = {
217 filtertable = {
218 'tempfile:': tempfilter,
218 'tempfile:': tempfilter,
219 'pipe:': pipefilter,
219 'pipe:': pipefilter,
220 }
220 }
221
221
222 def filter(s, cmd):
222 def filter(s, cmd):
223 "filter a string through a command that transforms its input to its output"
223 "filter a string through a command that transforms its input to its output"
224 for name, fn in filtertable.iteritems():
224 for name, fn in filtertable.iteritems():
225 if cmd.startswith(name):
225 if cmd.startswith(name):
226 return fn(s, cmd[len(name):].lstrip())
226 return fn(s, cmd[len(name):].lstrip())
227 return pipefilter(s, cmd)
227 return pipefilter(s, cmd)
228
228
229 def binary(s):
229 def binary(s):
230 """return true if a string is binary data"""
230 """return true if a string is binary data"""
231 return bool(s and '\0' in s)
231 return bool(s and '\0' in s)
232
232
233 def increasingchunks(source, min=1024, max=65536):
233 def increasingchunks(source, min=1024, max=65536):
234 '''return no less than min bytes per chunk while data remains,
234 '''return no less than min bytes per chunk while data remains,
235 doubling min after each chunk until it reaches max'''
235 doubling min after each chunk until it reaches max'''
236 def log2(x):
236 def log2(x):
237 if not x:
237 if not x:
238 return 0
238 return 0
239 i = 0
239 i = 0
240 while x:
240 while x:
241 x >>= 1
241 x >>= 1
242 i += 1
242 i += 1
243 return i - 1
243 return i - 1
244
244
245 buf = []
245 buf = []
246 blen = 0
246 blen = 0
247 for chunk in source:
247 for chunk in source:
248 buf.append(chunk)
248 buf.append(chunk)
249 blen += len(chunk)
249 blen += len(chunk)
250 if blen >= min:
250 if blen >= min:
251 if min < max:
251 if min < max:
252 min = min << 1
252 min = min << 1
253 nmin = 1 << log2(blen)
253 nmin = 1 << log2(blen)
254 if nmin > min:
254 if nmin > min:
255 min = nmin
255 min = nmin
256 if min > max:
256 if min > max:
257 min = max
257 min = max
258 yield ''.join(buf)
258 yield ''.join(buf)
259 blen = 0
259 blen = 0
260 buf = []
260 buf = []
261 if buf:
261 if buf:
262 yield ''.join(buf)
262 yield ''.join(buf)
263
263
264 Abort = error.Abort
264 Abort = error.Abort
265
265
266 def always(fn):
266 def always(fn):
267 return True
267 return True
268
268
269 def never(fn):
269 def never(fn):
270 return False
270 return False
271
271
272 def pathto(root, n1, n2):
272 def pathto(root, n1, n2):
273 '''return the relative path from one place to another.
273 '''return the relative path from one place to another.
274 root should use os.sep to separate directories
274 root should use os.sep to separate directories
275 n1 should use os.sep to separate directories
275 n1 should use os.sep to separate directories
276 n2 should use "/" to separate directories
276 n2 should use "/" to separate directories
277 returns an os.sep-separated path.
277 returns an os.sep-separated path.
278
278
279 If n1 is a relative path, it's assumed it's
279 If n1 is a relative path, it's assumed it's
280 relative to root.
280 relative to root.
281 n2 should always be relative to root.
281 n2 should always be relative to root.
282 '''
282 '''
283 if not n1:
283 if not n1:
284 return localpath(n2)
284 return localpath(n2)
285 if os.path.isabs(n1):
285 if os.path.isabs(n1):
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 return os.path.join(root, localpath(n2))
287 return os.path.join(root, localpath(n2))
288 n2 = '/'.join((pconvert(root), n2))
288 n2 = '/'.join((pconvert(root), n2))
289 a, b = splitpath(n1), n2.split('/')
289 a, b = splitpath(n1), n2.split('/')
290 a.reverse()
290 a.reverse()
291 b.reverse()
291 b.reverse()
292 while a and b and a[-1] == b[-1]:
292 while a and b and a[-1] == b[-1]:
293 a.pop()
293 a.pop()
294 b.pop()
294 b.pop()
295 b.reverse()
295 b.reverse()
296 return os.sep.join((['..'] * len(a)) + b) or '.'
296 return os.sep.join((['..'] * len(a)) + b) or '.'
297
297
298 _hgexecutable = None
298 _hgexecutable = None
299
299
300 def mainfrozen():
300 def mainfrozen():
301 """return True if we are a frozen executable.
301 """return True if we are a frozen executable.
302
302
303 The code supports py2exe (most common, Windows only) and tools/freeze
303 The code supports py2exe (most common, Windows only) and tools/freeze
304 (portable, not much used).
304 (portable, not much used).
305 """
305 """
306 return (hasattr(sys, "frozen") or # new py2exe
306 return (hasattr(sys, "frozen") or # new py2exe
307 hasattr(sys, "importers") or # old py2exe
307 hasattr(sys, "importers") or # old py2exe
308 imp.is_frozen("__main__")) # tools/freeze
308 imp.is_frozen("__main__")) # tools/freeze
309
309
310 def hgexecutable():
310 def hgexecutable():
311 """return location of the 'hg' executable.
311 """return location of the 'hg' executable.
312
312
313 Defaults to $HG or 'hg' in the search path.
313 Defaults to $HG or 'hg' in the search path.
314 """
314 """
315 if _hgexecutable is None:
315 if _hgexecutable is None:
316 hg = os.environ.get('HG')
316 hg = os.environ.get('HG')
317 if hg:
317 if hg:
318 _sethgexecutable(hg)
318 _sethgexecutable(hg)
319 elif mainfrozen():
319 elif mainfrozen():
320 _sethgexecutable(sys.executable)
320 _sethgexecutable(sys.executable)
321 else:
321 else:
322 exe = find_exe('hg') or os.path.basename(sys.argv[0])
322 exe = find_exe('hg') or os.path.basename(sys.argv[0])
323 _sethgexecutable(exe)
323 _sethgexecutable(exe)
324 return _hgexecutable
324 return _hgexecutable
325
325
326 def _sethgexecutable(path):
326 def _sethgexecutable(path):
327 """set location of the 'hg' executable"""
327 """set location of the 'hg' executable"""
328 global _hgexecutable
328 global _hgexecutable
329 _hgexecutable = path
329 _hgexecutable = path
330
330
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
332 '''enhanced shell command execution.
332 '''enhanced shell command execution.
333 run with environment maybe modified, maybe in different dir.
333 run with environment maybe modified, maybe in different dir.
334
334
335 if command fails and onerr is None, return status. if ui object,
335 if command fails and onerr is None, return status. if ui object,
336 print error message and return status, else raise onerr object as
336 print error message and return status, else raise onerr object as
337 exception.
337 exception.
338
338
339 if out is specified, it is assumed to be a file-like object that has a
339 if out is specified, it is assumed to be a file-like object that has a
340 write() method. stdout and stderr will be redirected to out.'''
340 write() method. stdout and stderr will be redirected to out.'''
341 try:
341 try:
342 sys.stdout.flush()
342 sys.stdout.flush()
343 except Exception:
343 except Exception:
344 pass
344 pass
345 def py2shell(val):
345 def py2shell(val):
346 'convert python object into string that is useful to shell'
346 'convert python object into string that is useful to shell'
347 if val is None or val is False:
347 if val is None or val is False:
348 return '0'
348 return '0'
349 if val is True:
349 if val is True:
350 return '1'
350 return '1'
351 return str(val)
351 return str(val)
352 origcmd = cmd
352 origcmd = cmd
353 cmd = quotecommand(cmd)
353 cmd = quotecommand(cmd)
354 env = dict(os.environ)
354 env = dict(os.environ)
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
356 env['HG'] = hgexecutable()
356 env['HG'] = hgexecutable()
357 if out is None:
357 if out is None:
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
359 env=env, cwd=cwd)
359 env=env, cwd=cwd)
360 else:
360 else:
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
363 stderr=subprocess.STDOUT)
363 stderr=subprocess.STDOUT)
364 for line in proc.stdout:
364 for line in proc.stdout:
365 out.write(line)
365 out.write(line)
366 proc.wait()
366 proc.wait()
367 rc = proc.returncode
367 rc = proc.returncode
368 if sys.platform == 'OpenVMS' and rc & 1:
368 if sys.platform == 'OpenVMS' and rc & 1:
369 rc = 0
369 rc = 0
370 if rc and onerr:
370 if rc and onerr:
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 explain_exit(rc)[0])
372 explain_exit(rc)[0])
373 if errprefix:
373 if errprefix:
374 errmsg = '%s: %s' % (errprefix, errmsg)
374 errmsg = '%s: %s' % (errprefix, errmsg)
375 try:
375 try:
376 onerr.warn(errmsg + '\n')
376 onerr.warn(errmsg + '\n')
377 except AttributeError:
377 except AttributeError:
378 raise onerr(errmsg)
378 raise onerr(errmsg)
379 return rc
379 return rc
380
380
381 def checksignature(func):
381 def checksignature(func):
382 '''wrap a function with code to check for calling errors'''
382 '''wrap a function with code to check for calling errors'''
383 def check(*args, **kwargs):
383 def check(*args, **kwargs):
384 try:
384 try:
385 return func(*args, **kwargs)
385 return func(*args, **kwargs)
386 except TypeError:
386 except TypeError:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 raise error.SignatureError
388 raise error.SignatureError
389 raise
389 raise
390
390
391 return check
391 return check
392
392
393 def makedir(path, notindexed):
393 def makedir(path, notindexed):
394 os.mkdir(path)
394 os.mkdir(path)
395
395
396 def unlinkpath(f):
396 def unlinkpath(f):
397 """unlink and remove the directory if it is empty"""
397 """unlink and remove the directory if it is empty"""
398 os.unlink(f)
398 os.unlink(f)
399 # try removing directories that might now be empty
399 # try removing directories that might now be empty
400 try:
400 try:
401 os.removedirs(os.path.dirname(f))
401 os.removedirs(os.path.dirname(f))
402 except OSError:
402 except OSError:
403 pass
403 pass
404
404
405 def copyfile(src, dest):
405 def copyfile(src, dest):
406 "copy a file, preserving mode and atime/mtime"
406 "copy a file, preserving mode and atime/mtime"
407 if os.path.islink(src):
407 if os.path.islink(src):
408 try:
408 try:
409 os.unlink(dest)
409 os.unlink(dest)
410 except OSError:
410 except OSError:
411 pass
411 pass
412 os.symlink(os.readlink(src), dest)
412 os.symlink(os.readlink(src), dest)
413 else:
413 else:
414 try:
414 try:
415 shutil.copyfile(src, dest)
415 shutil.copyfile(src, dest)
416 shutil.copymode(src, dest)
416 shutil.copymode(src, dest)
417 except shutil.Error, inst:
417 except shutil.Error, inst:
418 raise Abort(str(inst))
418 raise Abort(str(inst))
419
419
420 def copyfiles(src, dst, hardlink=None):
420 def copyfiles(src, dst, hardlink=None):
421 """Copy a directory tree using hardlinks if possible"""
421 """Copy a directory tree using hardlinks if possible"""
422
422
423 if hardlink is None:
423 if hardlink is None:
424 hardlink = (os.stat(src).st_dev ==
424 hardlink = (os.stat(src).st_dev ==
425 os.stat(os.path.dirname(dst)).st_dev)
425 os.stat(os.path.dirname(dst)).st_dev)
426
426
427 num = 0
427 num = 0
428 if os.path.isdir(src):
428 if os.path.isdir(src):
429 os.mkdir(dst)
429 os.mkdir(dst)
430 for name, kind in osutil.listdir(src):
430 for name, kind in osutil.listdir(src):
431 srcname = os.path.join(src, name)
431 srcname = os.path.join(src, name)
432 dstname = os.path.join(dst, name)
432 dstname = os.path.join(dst, name)
433 hardlink, n = copyfiles(srcname, dstname, hardlink)
433 hardlink, n = copyfiles(srcname, dstname, hardlink)
434 num += n
434 num += n
435 else:
435 else:
436 if hardlink:
436 if hardlink:
437 try:
437 try:
438 os_link(src, dst)
438 os_link(src, dst)
439 except (IOError, OSError):
439 except (IOError, OSError):
440 hardlink = False
440 hardlink = False
441 shutil.copy(src, dst)
441 shutil.copy(src, dst)
442 else:
442 else:
443 shutil.copy(src, dst)
443 shutil.copy(src, dst)
444 num += 1
444 num += 1
445
445
446 return hardlink, num
446 return hardlink, num
447
447
448 _windows_reserved_filenames = '''con prn aux nul
448 _windows_reserved_filenames = '''con prn aux nul
449 com1 com2 com3 com4 com5 com6 com7 com8 com9
449 com1 com2 com3 com4 com5 com6 com7 com8 com9
450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
451 _windows_reserved_chars = ':*?"<>|'
451 _windows_reserved_chars = ':*?"<>|'
452 def checkwinfilename(path):
452 def checkwinfilename(path):
453 '''Check that the base-relative path is a valid filename on Windows.
453 '''Check that the base-relative path is a valid filename on Windows.
454 Returns None if the path is ok, or a UI string describing the problem.
454 Returns None if the path is ok, or a UI string describing the problem.
455
455
456 >>> checkwinfilename("just/a/normal/path")
456 >>> checkwinfilename("just/a/normal/path")
457 >>> checkwinfilename("foo/bar/con.xml")
457 >>> checkwinfilename("foo/bar/con.xml")
458 "filename contains 'con', which is reserved on Windows"
458 "filename contains 'con', which is reserved on Windows"
459 >>> checkwinfilename("foo/con.xml/bar")
459 >>> checkwinfilename("foo/con.xml/bar")
460 "filename contains 'con', which is reserved on Windows"
460 "filename contains 'con', which is reserved on Windows"
461 >>> checkwinfilename("foo/bar/xml.con")
461 >>> checkwinfilename("foo/bar/xml.con")
462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
463 "filename contains 'AUX', which is reserved on Windows"
463 "filename contains 'AUX', which is reserved on Windows"
464 >>> checkwinfilename("foo/bar/bla:.txt")
464 >>> checkwinfilename("foo/bar/bla:.txt")
465 "filename contains ':', which is reserved on Windows"
465 "filename contains ':', which is reserved on Windows"
466 >>> checkwinfilename("foo/bar/b\07la.txt")
466 >>> checkwinfilename("foo/bar/b\07la.txt")
467 "filename contains '\\\\x07', which is invalid on Windows"
467 "filename contains '\\\\x07', which is invalid on Windows"
468 >>> checkwinfilename("foo/bar/bla ")
468 >>> checkwinfilename("foo/bar/bla ")
469 "filename ends with ' ', which is not allowed on Windows"
469 "filename ends with ' ', which is not allowed on Windows"
470 '''
470 '''
471 for n in path.replace('\\', '/').split('/'):
471 for n in path.replace('\\', '/').split('/'):
472 if not n:
472 if not n:
473 continue
473 continue
474 for c in n:
474 for c in n:
475 if c in _windows_reserved_chars:
475 if c in _windows_reserved_chars:
476 return _("filename contains '%s', which is reserved "
476 return _("filename contains '%s', which is reserved "
477 "on Windows") % c
477 "on Windows") % c
478 if ord(c) <= 31:
478 if ord(c) <= 31:
479 return _("filename contains %r, which is invalid "
479 return _("filename contains %r, which is invalid "
480 "on Windows") % c
480 "on Windows") % c
481 base = n.split('.')[0]
481 base = n.split('.')[0]
482 if base and base.lower() in _windows_reserved_filenames:
482 if base and base.lower() in _windows_reserved_filenames:
483 return _("filename contains '%s', which is reserved "
483 return _("filename contains '%s', which is reserved "
484 "on Windows") % base
484 "on Windows") % base
485 t = n[-1]
485 t = n[-1]
486 if t in '. ':
486 if t in '. ':
487 return _("filename ends with '%s', which is not allowed "
487 return _("filename ends with '%s', which is not allowed "
488 "on Windows") % t
488 "on Windows") % t
489
489
490 def lookup_reg(key, name=None, scope=None):
490 def lookupreg(key, name=None, scope=None):
491 return None
491 return None
492
492
493 def hidewindow():
493 def hidewindow():
494 """Hide current shell window.
494 """Hide current shell window.
495
495
496 Used to hide the window opened when starting asynchronous
496 Used to hide the window opened when starting asynchronous
497 child process under Windows, unneeded on other systems.
497 child process under Windows, unneeded on other systems.
498 """
498 """
499 pass
499 pass
500
500
501 if os.name == 'nt':
501 if os.name == 'nt':
502 checkosfilename = checkwinfilename
502 checkosfilename = checkwinfilename
503 from windows import *
503 from windows import *
504 else:
504 else:
505 from posix import *
505 from posix import *
506
506
507 def makelock(info, pathname):
507 def makelock(info, pathname):
508 try:
508 try:
509 return os.symlink(info, pathname)
509 return os.symlink(info, pathname)
510 except OSError, why:
510 except OSError, why:
511 if why.errno == errno.EEXIST:
511 if why.errno == errno.EEXIST:
512 raise
512 raise
513 except AttributeError: # no symlink in os
513 except AttributeError: # no symlink in os
514 pass
514 pass
515
515
516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
517 os.write(ld, info)
517 os.write(ld, info)
518 os.close(ld)
518 os.close(ld)
519
519
520 def readlock(pathname):
520 def readlock(pathname):
521 try:
521 try:
522 return os.readlink(pathname)
522 return os.readlink(pathname)
523 except OSError, why:
523 except OSError, why:
524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
525 raise
525 raise
526 except AttributeError: # no symlink in os
526 except AttributeError: # no symlink in os
527 pass
527 pass
528 fp = posixfile(pathname)
528 fp = posixfile(pathname)
529 r = fp.read()
529 r = fp.read()
530 fp.close()
530 fp.close()
531 return r
531 return r
532
532
533 def fstat(fp):
533 def fstat(fp):
534 '''stat file object that may not have fileno method.'''
534 '''stat file object that may not have fileno method.'''
535 try:
535 try:
536 return os.fstat(fp.fileno())
536 return os.fstat(fp.fileno())
537 except AttributeError:
537 except AttributeError:
538 return os.stat(fp.name)
538 return os.stat(fp.name)
539
539
540 # File system features
540 # File system features
541
541
542 def checkcase(path):
542 def checkcase(path):
543 """
543 """
544 Check whether the given path is on a case-sensitive filesystem
544 Check whether the given path is on a case-sensitive filesystem
545
545
546 Requires a path (like /foo/.hg) ending with a foldable final
546 Requires a path (like /foo/.hg) ending with a foldable final
547 directory component.
547 directory component.
548 """
548 """
549 s1 = os.stat(path)
549 s1 = os.stat(path)
550 d, b = os.path.split(path)
550 d, b = os.path.split(path)
551 p2 = os.path.join(d, b.upper())
551 p2 = os.path.join(d, b.upper())
552 if path == p2:
552 if path == p2:
553 p2 = os.path.join(d, b.lower())
553 p2 = os.path.join(d, b.lower())
554 try:
554 try:
555 s2 = os.stat(p2)
555 s2 = os.stat(p2)
556 if s2 == s1:
556 if s2 == s1:
557 return False
557 return False
558 return True
558 return True
559 except OSError:
559 except OSError:
560 return True
560 return True
561
561
562 _fspathcache = {}
562 _fspathcache = {}
563 def fspath(name, root):
563 def fspath(name, root):
564 '''Get name in the case stored in the filesystem
564 '''Get name in the case stored in the filesystem
565
565
566 The name is either relative to root, or it is an absolute path starting
566 The name is either relative to root, or it is an absolute path starting
567 with root. Note that this function is unnecessary, and should not be
567 with root. Note that this function is unnecessary, and should not be
568 called, for case-sensitive filesystems (simply because it's expensive).
568 called, for case-sensitive filesystems (simply because it's expensive).
569 '''
569 '''
570 # If name is absolute, make it relative
570 # If name is absolute, make it relative
571 if name.lower().startswith(root.lower()):
571 if name.lower().startswith(root.lower()):
572 l = len(root)
572 l = len(root)
573 if name[l] == os.sep or name[l] == os.altsep:
573 if name[l] == os.sep or name[l] == os.altsep:
574 l = l + 1
574 l = l + 1
575 name = name[l:]
575 name = name[l:]
576
576
577 if not os.path.lexists(os.path.join(root, name)):
577 if not os.path.lexists(os.path.join(root, name)):
578 return None
578 return None
579
579
580 seps = os.sep
580 seps = os.sep
581 if os.altsep:
581 if os.altsep:
582 seps = seps + os.altsep
582 seps = seps + os.altsep
583 # Protect backslashes. This gets silly very quickly.
583 # Protect backslashes. This gets silly very quickly.
584 seps.replace('\\','\\\\')
584 seps.replace('\\','\\\\')
585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
586 dir = os.path.normcase(os.path.normpath(root))
586 dir = os.path.normcase(os.path.normpath(root))
587 result = []
587 result = []
588 for part, sep in pattern.findall(name):
588 for part, sep in pattern.findall(name):
589 if sep:
589 if sep:
590 result.append(sep)
590 result.append(sep)
591 continue
591 continue
592
592
593 if dir not in _fspathcache:
593 if dir not in _fspathcache:
594 _fspathcache[dir] = os.listdir(dir)
594 _fspathcache[dir] = os.listdir(dir)
595 contents = _fspathcache[dir]
595 contents = _fspathcache[dir]
596
596
597 lpart = part.lower()
597 lpart = part.lower()
598 lenp = len(part)
598 lenp = len(part)
599 for n in contents:
599 for n in contents:
600 if lenp == len(n) and n.lower() == lpart:
600 if lenp == len(n) and n.lower() == lpart:
601 result.append(n)
601 result.append(n)
602 break
602 break
603 else:
603 else:
604 # Cannot happen, as the file exists!
604 # Cannot happen, as the file exists!
605 result.append(part)
605 result.append(part)
606 dir = os.path.join(dir, lpart)
606 dir = os.path.join(dir, lpart)
607
607
608 return ''.join(result)
608 return ''.join(result)
609
609
610 def checknlink(testfile):
610 def checknlink(testfile):
611 '''check whether hardlink count reporting works properly'''
611 '''check whether hardlink count reporting works properly'''
612
612
613 # testfile may be open, so we need a separate file for checking to
613 # testfile may be open, so we need a separate file for checking to
614 # work around issue2543 (or testfile may get lost on Samba shares)
614 # work around issue2543 (or testfile may get lost on Samba shares)
615 f1 = testfile + ".hgtmp1"
615 f1 = testfile + ".hgtmp1"
616 if os.path.lexists(f1):
616 if os.path.lexists(f1):
617 return False
617 return False
618 try:
618 try:
619 posixfile(f1, 'w').close()
619 posixfile(f1, 'w').close()
620 except IOError:
620 except IOError:
621 return False
621 return False
622
622
623 f2 = testfile + ".hgtmp2"
623 f2 = testfile + ".hgtmp2"
624 fd = None
624 fd = None
625 try:
625 try:
626 try:
626 try:
627 os_link(f1, f2)
627 os_link(f1, f2)
628 except OSError:
628 except OSError:
629 return False
629 return False
630
630
631 # nlinks() may behave differently for files on Windows shares if
631 # nlinks() may behave differently for files on Windows shares if
632 # the file is open.
632 # the file is open.
633 fd = posixfile(f2)
633 fd = posixfile(f2)
634 return nlinks(f2) > 1
634 return nlinks(f2) > 1
635 finally:
635 finally:
636 if fd is not None:
636 if fd is not None:
637 fd.close()
637 fd.close()
638 for f in (f1, f2):
638 for f in (f1, f2):
639 try:
639 try:
640 os.unlink(f)
640 os.unlink(f)
641 except OSError:
641 except OSError:
642 pass
642 pass
643
643
644 return False
644 return False
645
645
646 def endswithsep(path):
646 def endswithsep(path):
647 '''Check path ends with os.sep or os.altsep.'''
647 '''Check path ends with os.sep or os.altsep.'''
648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
649
649
650 def splitpath(path):
650 def splitpath(path):
651 '''Split path by os.sep.
651 '''Split path by os.sep.
652 Note that this function does not use os.altsep because this is
652 Note that this function does not use os.altsep because this is
653 an alternative of simple "xxx.split(os.sep)".
653 an alternative of simple "xxx.split(os.sep)".
654 It is recommended to use os.path.normpath() before using this
654 It is recommended to use os.path.normpath() before using this
655 function if need.'''
655 function if need.'''
656 return path.split(os.sep)
656 return path.split(os.sep)
657
657
658 def gui():
658 def gui():
659 '''Are we running in a GUI?'''
659 '''Are we running in a GUI?'''
660 if sys.platform == 'darwin':
660 if sys.platform == 'darwin':
661 if 'SSH_CONNECTION' in os.environ:
661 if 'SSH_CONNECTION' in os.environ:
662 # handle SSH access to a box where the user is logged in
662 # handle SSH access to a box where the user is logged in
663 return False
663 return False
664 elif getattr(osutil, 'isgui', None):
664 elif getattr(osutil, 'isgui', None):
665 # check if a CoreGraphics session is available
665 # check if a CoreGraphics session is available
666 return osutil.isgui()
666 return osutil.isgui()
667 else:
667 else:
668 # pure build; use a safe default
668 # pure build; use a safe default
669 return True
669 return True
670 else:
670 else:
671 return os.name == "nt" or os.environ.get("DISPLAY")
671 return os.name == "nt" or os.environ.get("DISPLAY")
672
672
673 def mktempcopy(name, emptyok=False, createmode=None):
673 def mktempcopy(name, emptyok=False, createmode=None):
674 """Create a temporary file with the same contents from name
674 """Create a temporary file with the same contents from name
675
675
676 The permission bits are copied from the original file.
676 The permission bits are copied from the original file.
677
677
678 If the temporary file is going to be truncated immediately, you
678 If the temporary file is going to be truncated immediately, you
679 can use emptyok=True as an optimization.
679 can use emptyok=True as an optimization.
680
680
681 Returns the name of the temporary file.
681 Returns the name of the temporary file.
682 """
682 """
683 d, fn = os.path.split(name)
683 d, fn = os.path.split(name)
684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
685 os.close(fd)
685 os.close(fd)
686 # Temporary files are created with mode 0600, which is usually not
686 # Temporary files are created with mode 0600, which is usually not
687 # what we want. If the original file already exists, just copy
687 # what we want. If the original file already exists, just copy
688 # its mode. Otherwise, manually obey umask.
688 # its mode. Otherwise, manually obey umask.
689 try:
689 try:
690 st_mode = os.lstat(name).st_mode & 0777
690 st_mode = os.lstat(name).st_mode & 0777
691 except OSError, inst:
691 except OSError, inst:
692 if inst.errno != errno.ENOENT:
692 if inst.errno != errno.ENOENT:
693 raise
693 raise
694 st_mode = createmode
694 st_mode = createmode
695 if st_mode is None:
695 if st_mode is None:
696 st_mode = ~umask
696 st_mode = ~umask
697 st_mode &= 0666
697 st_mode &= 0666
698 os.chmod(temp, st_mode)
698 os.chmod(temp, st_mode)
699 if emptyok:
699 if emptyok:
700 return temp
700 return temp
701 try:
701 try:
702 try:
702 try:
703 ifp = posixfile(name, "rb")
703 ifp = posixfile(name, "rb")
704 except IOError, inst:
704 except IOError, inst:
705 if inst.errno == errno.ENOENT:
705 if inst.errno == errno.ENOENT:
706 return temp
706 return temp
707 if not getattr(inst, 'filename', None):
707 if not getattr(inst, 'filename', None):
708 inst.filename = name
708 inst.filename = name
709 raise
709 raise
710 ofp = posixfile(temp, "wb")
710 ofp = posixfile(temp, "wb")
711 for chunk in filechunkiter(ifp):
711 for chunk in filechunkiter(ifp):
712 ofp.write(chunk)
712 ofp.write(chunk)
713 ifp.close()
713 ifp.close()
714 ofp.close()
714 ofp.close()
715 except:
715 except:
716 try: os.unlink(temp)
716 try: os.unlink(temp)
717 except: pass
717 except: pass
718 raise
718 raise
719 return temp
719 return temp
720
720
721 class atomictempfile(object):
721 class atomictempfile(object):
722 '''writeable file object that atomically updates a file
722 '''writeable file object that atomically updates a file
723
723
724 All writes will go to a temporary copy of the original file. Call
724 All writes will go to a temporary copy of the original file. Call
725 rename() when you are done writing, and atomictempfile will rename
725 rename() when you are done writing, and atomictempfile will rename
726 the temporary copy to the original name, making the changes visible.
726 the temporary copy to the original name, making the changes visible.
727
727
728 Unlike other file-like objects, close() discards your writes by
728 Unlike other file-like objects, close() discards your writes by
729 simply deleting the temporary file.
729 simply deleting the temporary file.
730 '''
730 '''
731 def __init__(self, name, mode='w+b', createmode=None):
731 def __init__(self, name, mode='w+b', createmode=None):
732 self.__name = name # permanent name
732 self.__name = name # permanent name
733 self._tempname = mktempcopy(name, emptyok=('w' in mode),
733 self._tempname = mktempcopy(name, emptyok=('w' in mode),
734 createmode=createmode)
734 createmode=createmode)
735 self._fp = posixfile(self._tempname, mode)
735 self._fp = posixfile(self._tempname, mode)
736
736
737 # delegated methods
737 # delegated methods
738 self.write = self._fp.write
738 self.write = self._fp.write
739 self.fileno = self._fp.fileno
739 self.fileno = self._fp.fileno
740
740
741 def rename(self):
741 def rename(self):
742 if not self._fp.closed:
742 if not self._fp.closed:
743 self._fp.close()
743 self._fp.close()
744 rename(self._tempname, localpath(self.__name))
744 rename(self._tempname, localpath(self.__name))
745
745
746 def close(self):
746 def close(self):
747 if not self._fp.closed:
747 if not self._fp.closed:
748 try:
748 try:
749 os.unlink(self._tempname)
749 os.unlink(self._tempname)
750 except OSError:
750 except OSError:
751 pass
751 pass
752 self._fp.close()
752 self._fp.close()
753
753
754 def __del__(self):
754 def __del__(self):
755 if hasattr(self, '_fp'): # constructor actually did something
755 if hasattr(self, '_fp'): # constructor actually did something
756 self.close()
756 self.close()
757
757
758 def makedirs(name, mode=None):
758 def makedirs(name, mode=None):
759 """recursive directory creation with parent mode inheritance"""
759 """recursive directory creation with parent mode inheritance"""
760 parent = os.path.abspath(os.path.dirname(name))
760 parent = os.path.abspath(os.path.dirname(name))
761 try:
761 try:
762 os.mkdir(name)
762 os.mkdir(name)
763 if mode is not None:
763 if mode is not None:
764 os.chmod(name, mode)
764 os.chmod(name, mode)
765 return
765 return
766 except OSError, err:
766 except OSError, err:
767 if err.errno == errno.EEXIST:
767 if err.errno == errno.EEXIST:
768 return
768 return
769 if not name or parent == name or err.errno != errno.ENOENT:
769 if not name or parent == name or err.errno != errno.ENOENT:
770 raise
770 raise
771 makedirs(parent, mode)
771 makedirs(parent, mode)
772 makedirs(name, mode)
772 makedirs(name, mode)
773
773
774 def readfile(path):
774 def readfile(path):
775 fp = open(path)
775 fp = open(path)
776 try:
776 try:
777 return fp.read()
777 return fp.read()
778 finally:
778 finally:
779 fp.close()
779 fp.close()
780
780
781 def writefile(path, text):
781 def writefile(path, text):
782 fp = open(path, 'wb')
782 fp = open(path, 'wb')
783 try:
783 try:
784 fp.write(text)
784 fp.write(text)
785 finally:
785 finally:
786 fp.close()
786 fp.close()
787
787
788 def appendfile(path, text):
788 def appendfile(path, text):
789 fp = open(path, 'ab')
789 fp = open(path, 'ab')
790 try:
790 try:
791 fp.write(text)
791 fp.write(text)
792 finally:
792 finally:
793 fp.close()
793 fp.close()
794
794
795 class chunkbuffer(object):
795 class chunkbuffer(object):
796 """Allow arbitrary sized chunks of data to be efficiently read from an
796 """Allow arbitrary sized chunks of data to be efficiently read from an
797 iterator over chunks of arbitrary size."""
797 iterator over chunks of arbitrary size."""
798
798
799 def __init__(self, in_iter):
799 def __init__(self, in_iter):
800 """in_iter is the iterator that's iterating over the input chunks.
800 """in_iter is the iterator that's iterating over the input chunks.
801 targetsize is how big a buffer to try to maintain."""
801 targetsize is how big a buffer to try to maintain."""
802 def splitbig(chunks):
802 def splitbig(chunks):
803 for chunk in chunks:
803 for chunk in chunks:
804 if len(chunk) > 2**20:
804 if len(chunk) > 2**20:
805 pos = 0
805 pos = 0
806 while pos < len(chunk):
806 while pos < len(chunk):
807 end = pos + 2 ** 18
807 end = pos + 2 ** 18
808 yield chunk[pos:end]
808 yield chunk[pos:end]
809 pos = end
809 pos = end
810 else:
810 else:
811 yield chunk
811 yield chunk
812 self.iter = splitbig(in_iter)
812 self.iter = splitbig(in_iter)
813 self._queue = []
813 self._queue = []
814
814
815 def read(self, l):
815 def read(self, l):
816 """Read L bytes of data from the iterator of chunks of data.
816 """Read L bytes of data from the iterator of chunks of data.
817 Returns less than L bytes if the iterator runs dry."""
817 Returns less than L bytes if the iterator runs dry."""
818 left = l
818 left = l
819 buf = ''
819 buf = ''
820 queue = self._queue
820 queue = self._queue
821 while left > 0:
821 while left > 0:
822 # refill the queue
822 # refill the queue
823 if not queue:
823 if not queue:
824 target = 2**18
824 target = 2**18
825 for chunk in self.iter:
825 for chunk in self.iter:
826 queue.append(chunk)
826 queue.append(chunk)
827 target -= len(chunk)
827 target -= len(chunk)
828 if target <= 0:
828 if target <= 0:
829 break
829 break
830 if not queue:
830 if not queue:
831 break
831 break
832
832
833 chunk = queue.pop(0)
833 chunk = queue.pop(0)
834 left -= len(chunk)
834 left -= len(chunk)
835 if left < 0:
835 if left < 0:
836 queue.insert(0, chunk[left:])
836 queue.insert(0, chunk[left:])
837 buf += chunk[:left]
837 buf += chunk[:left]
838 else:
838 else:
839 buf += chunk
839 buf += chunk
840
840
841 return buf
841 return buf
842
842
843 def filechunkiter(f, size=65536, limit=None):
843 def filechunkiter(f, size=65536, limit=None):
844 """Create a generator that produces the data in the file size
844 """Create a generator that produces the data in the file size
845 (default 65536) bytes at a time, up to optional limit (default is
845 (default 65536) bytes at a time, up to optional limit (default is
846 to read all data). Chunks may be less than size bytes if the
846 to read all data). Chunks may be less than size bytes if the
847 chunk is the last chunk in the file, or the file is a socket or
847 chunk is the last chunk in the file, or the file is a socket or
848 some other type of file that sometimes reads less data than is
848 some other type of file that sometimes reads less data than is
849 requested."""
849 requested."""
850 assert size >= 0
850 assert size >= 0
851 assert limit is None or limit >= 0
851 assert limit is None or limit >= 0
852 while True:
852 while True:
853 if limit is None:
853 if limit is None:
854 nbytes = size
854 nbytes = size
855 else:
855 else:
856 nbytes = min(limit, size)
856 nbytes = min(limit, size)
857 s = nbytes and f.read(nbytes)
857 s = nbytes and f.read(nbytes)
858 if not s:
858 if not s:
859 break
859 break
860 if limit:
860 if limit:
861 limit -= len(s)
861 limit -= len(s)
862 yield s
862 yield s
863
863
864 def makedate():
864 def makedate():
865 lt = time.localtime()
865 lt = time.localtime()
866 if lt[8] == 1 and time.daylight:
866 if lt[8] == 1 and time.daylight:
867 tz = time.altzone
867 tz = time.altzone
868 else:
868 else:
869 tz = time.timezone
869 tz = time.timezone
870 t = time.mktime(lt)
870 t = time.mktime(lt)
871 if t < 0:
871 if t < 0:
872 hint = _("check your clock")
872 hint = _("check your clock")
873 raise Abort(_("negative timestamp: %d") % t, hint=hint)
873 raise Abort(_("negative timestamp: %d") % t, hint=hint)
874 return t, tz
874 return t, tz
875
875
876 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
876 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
877 """represent a (unixtime, offset) tuple as a localized time.
877 """represent a (unixtime, offset) tuple as a localized time.
878 unixtime is seconds since the epoch, and offset is the time zone's
878 unixtime is seconds since the epoch, and offset is the time zone's
879 number of seconds away from UTC. if timezone is false, do not
879 number of seconds away from UTC. if timezone is false, do not
880 append time zone to string."""
880 append time zone to string."""
881 t, tz = date or makedate()
881 t, tz = date or makedate()
882 if t < 0:
882 if t < 0:
883 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
883 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
884 tz = 0
884 tz = 0
885 if "%1" in format or "%2" in format:
885 if "%1" in format or "%2" in format:
886 sign = (tz > 0) and "-" or "+"
886 sign = (tz > 0) and "-" or "+"
887 minutes = abs(tz) // 60
887 minutes = abs(tz) // 60
888 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
888 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
889 format = format.replace("%2", "%02d" % (minutes % 60))
889 format = format.replace("%2", "%02d" % (minutes % 60))
890 s = time.strftime(format, time.gmtime(float(t) - tz))
890 s = time.strftime(format, time.gmtime(float(t) - tz))
891 return s
891 return s
892
892
893 def shortdate(date=None):
893 def shortdate(date=None):
894 """turn (timestamp, tzoff) tuple into iso 8631 date."""
894 """turn (timestamp, tzoff) tuple into iso 8631 date."""
895 return datestr(date, format='%Y-%m-%d')
895 return datestr(date, format='%Y-%m-%d')
896
896
897 def strdate(string, format, defaults=[]):
897 def strdate(string, format, defaults=[]):
898 """parse a localized time string and return a (unixtime, offset) tuple.
898 """parse a localized time string and return a (unixtime, offset) tuple.
899 if the string cannot be parsed, ValueError is raised."""
899 if the string cannot be parsed, ValueError is raised."""
900 def timezone(string):
900 def timezone(string):
901 tz = string.split()[-1]
901 tz = string.split()[-1]
902 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
902 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
903 sign = (tz[0] == "+") and 1 or -1
903 sign = (tz[0] == "+") and 1 or -1
904 hours = int(tz[1:3])
904 hours = int(tz[1:3])
905 minutes = int(tz[3:5])
905 minutes = int(tz[3:5])
906 return -sign * (hours * 60 + minutes) * 60
906 return -sign * (hours * 60 + minutes) * 60
907 if tz == "GMT" or tz == "UTC":
907 if tz == "GMT" or tz == "UTC":
908 return 0
908 return 0
909 return None
909 return None
910
910
911 # NOTE: unixtime = localunixtime + offset
911 # NOTE: unixtime = localunixtime + offset
912 offset, date = timezone(string), string
912 offset, date = timezone(string), string
913 if offset is not None:
913 if offset is not None:
914 date = " ".join(string.split()[:-1])
914 date = " ".join(string.split()[:-1])
915
915
916 # add missing elements from defaults
916 # add missing elements from defaults
917 usenow = False # default to using biased defaults
917 usenow = False # default to using biased defaults
918 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
918 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
919 found = [True for p in part if ("%"+p) in format]
919 found = [True for p in part if ("%"+p) in format]
920 if not found:
920 if not found:
921 date += "@" + defaults[part][usenow]
921 date += "@" + defaults[part][usenow]
922 format += "@%" + part[0]
922 format += "@%" + part[0]
923 else:
923 else:
924 # We've found a specific time element, less specific time
924 # We've found a specific time element, less specific time
925 # elements are relative to today
925 # elements are relative to today
926 usenow = True
926 usenow = True
927
927
928 timetuple = time.strptime(date, format)
928 timetuple = time.strptime(date, format)
929 localunixtime = int(calendar.timegm(timetuple))
929 localunixtime = int(calendar.timegm(timetuple))
930 if offset is None:
930 if offset is None:
931 # local timezone
931 # local timezone
932 unixtime = int(time.mktime(timetuple))
932 unixtime = int(time.mktime(timetuple))
933 offset = unixtime - localunixtime
933 offset = unixtime - localunixtime
934 else:
934 else:
935 unixtime = localunixtime + offset
935 unixtime = localunixtime + offset
936 return unixtime, offset
936 return unixtime, offset
937
937
938 def parsedate(date, formats=None, bias={}):
938 def parsedate(date, formats=None, bias={}):
939 """parse a localized date/time and return a (unixtime, offset) tuple.
939 """parse a localized date/time and return a (unixtime, offset) tuple.
940
940
941 The date may be a "unixtime offset" string or in one of the specified
941 The date may be a "unixtime offset" string or in one of the specified
942 formats. If the date already is a (unixtime, offset) tuple, it is returned.
942 formats. If the date already is a (unixtime, offset) tuple, it is returned.
943 """
943 """
944 if not date:
944 if not date:
945 return 0, 0
945 return 0, 0
946 if isinstance(date, tuple) and len(date) == 2:
946 if isinstance(date, tuple) and len(date) == 2:
947 return date
947 return date
948 if not formats:
948 if not formats:
949 formats = defaultdateformats
949 formats = defaultdateformats
950 date = date.strip()
950 date = date.strip()
951 try:
951 try:
952 when, offset = map(int, date.split(' '))
952 when, offset = map(int, date.split(' '))
953 except ValueError:
953 except ValueError:
954 # fill out defaults
954 # fill out defaults
955 now = makedate()
955 now = makedate()
956 defaults = {}
956 defaults = {}
957 for part in ("d", "mb", "yY", "HI", "M", "S"):
957 for part in ("d", "mb", "yY", "HI", "M", "S"):
958 # this piece is for rounding the specific end of unknowns
958 # this piece is for rounding the specific end of unknowns
959 b = bias.get(part)
959 b = bias.get(part)
960 if b is None:
960 if b is None:
961 if part[0] in "HMS":
961 if part[0] in "HMS":
962 b = "00"
962 b = "00"
963 else:
963 else:
964 b = "0"
964 b = "0"
965
965
966 # this piece is for matching the generic end to today's date
966 # this piece is for matching the generic end to today's date
967 n = datestr(now, "%" + part[0])
967 n = datestr(now, "%" + part[0])
968
968
969 defaults[part] = (b, n)
969 defaults[part] = (b, n)
970
970
971 for format in formats:
971 for format in formats:
972 try:
972 try:
973 when, offset = strdate(date, format, defaults)
973 when, offset = strdate(date, format, defaults)
974 except (ValueError, OverflowError):
974 except (ValueError, OverflowError):
975 pass
975 pass
976 else:
976 else:
977 break
977 break
978 else:
978 else:
979 raise Abort(_('invalid date: %r') % date)
979 raise Abort(_('invalid date: %r') % date)
980 # validate explicit (probably user-specified) date and
980 # validate explicit (probably user-specified) date and
981 # time zone offset. values must fit in signed 32 bits for
981 # time zone offset. values must fit in signed 32 bits for
982 # current 32-bit linux runtimes. timezones go from UTC-12
982 # current 32-bit linux runtimes. timezones go from UTC-12
983 # to UTC+14
983 # to UTC+14
984 if abs(when) > 0x7fffffff:
984 if abs(when) > 0x7fffffff:
985 raise Abort(_('date exceeds 32 bits: %d') % when)
985 raise Abort(_('date exceeds 32 bits: %d') % when)
986 if when < 0:
986 if when < 0:
987 raise Abort(_('negative date value: %d') % when)
987 raise Abort(_('negative date value: %d') % when)
988 if offset < -50400 or offset > 43200:
988 if offset < -50400 or offset > 43200:
989 raise Abort(_('impossible time zone offset: %d') % offset)
989 raise Abort(_('impossible time zone offset: %d') % offset)
990 return when, offset
990 return when, offset
991
991
992 def matchdate(date):
992 def matchdate(date):
993 """Return a function that matches a given date match specifier
993 """Return a function that matches a given date match specifier
994
994
995 Formats include:
995 Formats include:
996
996
997 '{date}' match a given date to the accuracy provided
997 '{date}' match a given date to the accuracy provided
998
998
999 '<{date}' on or before a given date
999 '<{date}' on or before a given date
1000
1000
1001 '>{date}' on or after a given date
1001 '>{date}' on or after a given date
1002
1002
1003 >>> p1 = parsedate("10:29:59")
1003 >>> p1 = parsedate("10:29:59")
1004 >>> p2 = parsedate("10:30:00")
1004 >>> p2 = parsedate("10:30:00")
1005 >>> p3 = parsedate("10:30:59")
1005 >>> p3 = parsedate("10:30:59")
1006 >>> p4 = parsedate("10:31:00")
1006 >>> p4 = parsedate("10:31:00")
1007 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1007 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1008 >>> f = matchdate("10:30")
1008 >>> f = matchdate("10:30")
1009 >>> f(p1[0])
1009 >>> f(p1[0])
1010 False
1010 False
1011 >>> f(p2[0])
1011 >>> f(p2[0])
1012 True
1012 True
1013 >>> f(p3[0])
1013 >>> f(p3[0])
1014 True
1014 True
1015 >>> f(p4[0])
1015 >>> f(p4[0])
1016 False
1016 False
1017 >>> f(p5[0])
1017 >>> f(p5[0])
1018 False
1018 False
1019 """
1019 """
1020
1020
1021 def lower(date):
1021 def lower(date):
1022 d = dict(mb="1", d="1")
1022 d = dict(mb="1", d="1")
1023 return parsedate(date, extendeddateformats, d)[0]
1023 return parsedate(date, extendeddateformats, d)[0]
1024
1024
1025 def upper(date):
1025 def upper(date):
1026 d = dict(mb="12", HI="23", M="59", S="59")
1026 d = dict(mb="12", HI="23", M="59", S="59")
1027 for days in ("31", "30", "29"):
1027 for days in ("31", "30", "29"):
1028 try:
1028 try:
1029 d["d"] = days
1029 d["d"] = days
1030 return parsedate(date, extendeddateformats, d)[0]
1030 return parsedate(date, extendeddateformats, d)[0]
1031 except:
1031 except:
1032 pass
1032 pass
1033 d["d"] = "28"
1033 d["d"] = "28"
1034 return parsedate(date, extendeddateformats, d)[0]
1034 return parsedate(date, extendeddateformats, d)[0]
1035
1035
1036 date = date.strip()
1036 date = date.strip()
1037
1037
1038 if not date:
1038 if not date:
1039 raise Abort(_("dates cannot consist entirely of whitespace"))
1039 raise Abort(_("dates cannot consist entirely of whitespace"))
1040 elif date[0] == "<":
1040 elif date[0] == "<":
1041 if not date[1:]:
1041 if not date[1:]:
1042 raise Abort(_("invalid day spec, use '<DATE'"))
1042 raise Abort(_("invalid day spec, use '<DATE'"))
1043 when = upper(date[1:])
1043 when = upper(date[1:])
1044 return lambda x: x <= when
1044 return lambda x: x <= when
1045 elif date[0] == ">":
1045 elif date[0] == ">":
1046 if not date[1:]:
1046 if not date[1:]:
1047 raise Abort(_("invalid day spec, use '>DATE'"))
1047 raise Abort(_("invalid day spec, use '>DATE'"))
1048 when = lower(date[1:])
1048 when = lower(date[1:])
1049 return lambda x: x >= when
1049 return lambda x: x >= when
1050 elif date[0] == "-":
1050 elif date[0] == "-":
1051 try:
1051 try:
1052 days = int(date[1:])
1052 days = int(date[1:])
1053 except ValueError:
1053 except ValueError:
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1055 if days < 0:
1055 if days < 0:
1056 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1056 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1057 % date[1:])
1057 % date[1:])
1058 when = makedate()[0] - days * 3600 * 24
1058 when = makedate()[0] - days * 3600 * 24
1059 return lambda x: x >= when
1059 return lambda x: x >= when
1060 elif " to " in date:
1060 elif " to " in date:
1061 a, b = date.split(" to ")
1061 a, b = date.split(" to ")
1062 start, stop = lower(a), upper(b)
1062 start, stop = lower(a), upper(b)
1063 return lambda x: x >= start and x <= stop
1063 return lambda x: x >= start and x <= stop
1064 else:
1064 else:
1065 start, stop = lower(date), upper(date)
1065 start, stop = lower(date), upper(date)
1066 return lambda x: x >= start and x <= stop
1066 return lambda x: x >= start and x <= stop
1067
1067
1068 def shortuser(user):
1068 def shortuser(user):
1069 """Return a short representation of a user name or email address."""
1069 """Return a short representation of a user name or email address."""
1070 f = user.find('@')
1070 f = user.find('@')
1071 if f >= 0:
1071 if f >= 0:
1072 user = user[:f]
1072 user = user[:f]
1073 f = user.find('<')
1073 f = user.find('<')
1074 if f >= 0:
1074 if f >= 0:
1075 user = user[f + 1:]
1075 user = user[f + 1:]
1076 f = user.find(' ')
1076 f = user.find(' ')
1077 if f >= 0:
1077 if f >= 0:
1078 user = user[:f]
1078 user = user[:f]
1079 f = user.find('.')
1079 f = user.find('.')
1080 if f >= 0:
1080 if f >= 0:
1081 user = user[:f]
1081 user = user[:f]
1082 return user
1082 return user
1083
1083
1084 def email(author):
1084 def email(author):
1085 '''get email of author.'''
1085 '''get email of author.'''
1086 r = author.find('>')
1086 r = author.find('>')
1087 if r == -1:
1087 if r == -1:
1088 r = None
1088 r = None
1089 return author[author.find('<') + 1:r]
1089 return author[author.find('<') + 1:r]
1090
1090
1091 def _ellipsis(text, maxlength):
1091 def _ellipsis(text, maxlength):
1092 if len(text) <= maxlength:
1092 if len(text) <= maxlength:
1093 return text, False
1093 return text, False
1094 else:
1094 else:
1095 return "%s..." % (text[:maxlength - 3]), True
1095 return "%s..." % (text[:maxlength - 3]), True
1096
1096
1097 def ellipsis(text, maxlength=400):
1097 def ellipsis(text, maxlength=400):
1098 """Trim string to at most maxlength (default: 400) characters."""
1098 """Trim string to at most maxlength (default: 400) characters."""
1099 try:
1099 try:
1100 # use unicode not to split at intermediate multi-byte sequence
1100 # use unicode not to split at intermediate multi-byte sequence
1101 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1101 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1102 maxlength)
1102 maxlength)
1103 if not truncated:
1103 if not truncated:
1104 return text
1104 return text
1105 return utext.encode(encoding.encoding)
1105 return utext.encode(encoding.encoding)
1106 except (UnicodeDecodeError, UnicodeEncodeError):
1106 except (UnicodeDecodeError, UnicodeEncodeError):
1107 return _ellipsis(text, maxlength)[0]
1107 return _ellipsis(text, maxlength)[0]
1108
1108
1109 def bytecount(nbytes):
1109 def bytecount(nbytes):
1110 '''return byte count formatted as readable string, with units'''
1110 '''return byte count formatted as readable string, with units'''
1111
1111
1112 units = (
1112 units = (
1113 (100, 1 << 30, _('%.0f GB')),
1113 (100, 1 << 30, _('%.0f GB')),
1114 (10, 1 << 30, _('%.1f GB')),
1114 (10, 1 << 30, _('%.1f GB')),
1115 (1, 1 << 30, _('%.2f GB')),
1115 (1, 1 << 30, _('%.2f GB')),
1116 (100, 1 << 20, _('%.0f MB')),
1116 (100, 1 << 20, _('%.0f MB')),
1117 (10, 1 << 20, _('%.1f MB')),
1117 (10, 1 << 20, _('%.1f MB')),
1118 (1, 1 << 20, _('%.2f MB')),
1118 (1, 1 << 20, _('%.2f MB')),
1119 (100, 1 << 10, _('%.0f KB')),
1119 (100, 1 << 10, _('%.0f KB')),
1120 (10, 1 << 10, _('%.1f KB')),
1120 (10, 1 << 10, _('%.1f KB')),
1121 (1, 1 << 10, _('%.2f KB')),
1121 (1, 1 << 10, _('%.2f KB')),
1122 (1, 1, _('%.0f bytes')),
1122 (1, 1, _('%.0f bytes')),
1123 )
1123 )
1124
1124
1125 for multiplier, divisor, format in units:
1125 for multiplier, divisor, format in units:
1126 if nbytes >= divisor * multiplier:
1126 if nbytes >= divisor * multiplier:
1127 return format % (nbytes / float(divisor))
1127 return format % (nbytes / float(divisor))
1128 return units[-1][2] % nbytes
1128 return units[-1][2] % nbytes
1129
1129
1130 def uirepr(s):
1130 def uirepr(s):
1131 # Avoid double backslash in Windows path repr()
1131 # Avoid double backslash in Windows path repr()
1132 return repr(s).replace('\\\\', '\\')
1132 return repr(s).replace('\\\\', '\\')
1133
1133
1134 # delay import of textwrap
1134 # delay import of textwrap
1135 def MBTextWrapper(**kwargs):
1135 def MBTextWrapper(**kwargs):
1136 class tw(textwrap.TextWrapper):
1136 class tw(textwrap.TextWrapper):
1137 """
1137 """
1138 Extend TextWrapper for double-width characters.
1138 Extend TextWrapper for double-width characters.
1139
1139
1140 Some Asian characters use two terminal columns instead of one.
1140 Some Asian characters use two terminal columns instead of one.
1141 A good example of this behavior can be seen with u'\u65e5\u672c',
1141 A good example of this behavior can be seen with u'\u65e5\u672c',
1142 the two Japanese characters for "Japan":
1142 the two Japanese characters for "Japan":
1143 len() returns 2, but when printed to a terminal, they eat 4 columns.
1143 len() returns 2, but when printed to a terminal, they eat 4 columns.
1144
1144
1145 (Note that this has nothing to do whatsoever with unicode
1145 (Note that this has nothing to do whatsoever with unicode
1146 representation, or encoding of the underlying string)
1146 representation, or encoding of the underlying string)
1147 """
1147 """
1148 def __init__(self, **kwargs):
1148 def __init__(self, **kwargs):
1149 textwrap.TextWrapper.__init__(self, **kwargs)
1149 textwrap.TextWrapper.__init__(self, **kwargs)
1150
1150
1151 def _cutdown(self, str, space_left):
1151 def _cutdown(self, str, space_left):
1152 l = 0
1152 l = 0
1153 ucstr = unicode(str, encoding.encoding)
1153 ucstr = unicode(str, encoding.encoding)
1154 colwidth = unicodedata.east_asian_width
1154 colwidth = unicodedata.east_asian_width
1155 for i in xrange(len(ucstr)):
1155 for i in xrange(len(ucstr)):
1156 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1156 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1157 if space_left < l:
1157 if space_left < l:
1158 return (ucstr[:i].encode(encoding.encoding),
1158 return (ucstr[:i].encode(encoding.encoding),
1159 ucstr[i:].encode(encoding.encoding))
1159 ucstr[i:].encode(encoding.encoding))
1160 return str, ''
1160 return str, ''
1161
1161
1162 # overriding of base class
1162 # overriding of base class
1163 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1163 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1164 space_left = max(width - cur_len, 1)
1164 space_left = max(width - cur_len, 1)
1165
1165
1166 if self.break_long_words:
1166 if self.break_long_words:
1167 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1167 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1168 cur_line.append(cut)
1168 cur_line.append(cut)
1169 reversed_chunks[-1] = res
1169 reversed_chunks[-1] = res
1170 elif not cur_line:
1170 elif not cur_line:
1171 cur_line.append(reversed_chunks.pop())
1171 cur_line.append(reversed_chunks.pop())
1172
1172
1173 global MBTextWrapper
1173 global MBTextWrapper
1174 MBTextWrapper = tw
1174 MBTextWrapper = tw
1175 return tw(**kwargs)
1175 return tw(**kwargs)
1176
1176
1177 def wrap(line, width, initindent='', hangindent=''):
1177 def wrap(line, width, initindent='', hangindent=''):
1178 maxindent = max(len(hangindent), len(initindent))
1178 maxindent = max(len(hangindent), len(initindent))
1179 if width <= maxindent:
1179 if width <= maxindent:
1180 # adjust for weird terminal size
1180 # adjust for weird terminal size
1181 width = max(78, maxindent + 1)
1181 width = max(78, maxindent + 1)
1182 wrapper = MBTextWrapper(width=width,
1182 wrapper = MBTextWrapper(width=width,
1183 initial_indent=initindent,
1183 initial_indent=initindent,
1184 subsequent_indent=hangindent)
1184 subsequent_indent=hangindent)
1185 return wrapper.fill(line)
1185 return wrapper.fill(line)
1186
1186
1187 def iterlines(iterator):
1187 def iterlines(iterator):
1188 for chunk in iterator:
1188 for chunk in iterator:
1189 for line in chunk.splitlines():
1189 for line in chunk.splitlines():
1190 yield line
1190 yield line
1191
1191
1192 def expandpath(path):
1192 def expandpath(path):
1193 return os.path.expanduser(os.path.expandvars(path))
1193 return os.path.expanduser(os.path.expandvars(path))
1194
1194
1195 def hgcmd():
1195 def hgcmd():
1196 """Return the command used to execute current hg
1196 """Return the command used to execute current hg
1197
1197
1198 This is different from hgexecutable() because on Windows we want
1198 This is different from hgexecutable() because on Windows we want
1199 to avoid things opening new shell windows like batch files, so we
1199 to avoid things opening new shell windows like batch files, so we
1200 get either the python call or current executable.
1200 get either the python call or current executable.
1201 """
1201 """
1202 if mainfrozen():
1202 if mainfrozen():
1203 return [sys.executable]
1203 return [sys.executable]
1204 return gethgcmd()
1204 return gethgcmd()
1205
1205
1206 def rundetached(args, condfn):
1206 def rundetached(args, condfn):
1207 """Execute the argument list in a detached process.
1207 """Execute the argument list in a detached process.
1208
1208
1209 condfn is a callable which is called repeatedly and should return
1209 condfn is a callable which is called repeatedly and should return
1210 True once the child process is known to have started successfully.
1210 True once the child process is known to have started successfully.
1211 At this point, the child process PID is returned. If the child
1211 At this point, the child process PID is returned. If the child
1212 process fails to start or finishes before condfn() evaluates to
1212 process fails to start or finishes before condfn() evaluates to
1213 True, return -1.
1213 True, return -1.
1214 """
1214 """
1215 # Windows case is easier because the child process is either
1215 # Windows case is easier because the child process is either
1216 # successfully starting and validating the condition or exiting
1216 # successfully starting and validating the condition or exiting
1217 # on failure. We just poll on its PID. On Unix, if the child
1217 # on failure. We just poll on its PID. On Unix, if the child
1218 # process fails to start, it will be left in a zombie state until
1218 # process fails to start, it will be left in a zombie state until
1219 # the parent wait on it, which we cannot do since we expect a long
1219 # the parent wait on it, which we cannot do since we expect a long
1220 # running process on success. Instead we listen for SIGCHLD telling
1220 # running process on success. Instead we listen for SIGCHLD telling
1221 # us our child process terminated.
1221 # us our child process terminated.
1222 terminated = set()
1222 terminated = set()
1223 def handler(signum, frame):
1223 def handler(signum, frame):
1224 terminated.add(os.wait())
1224 terminated.add(os.wait())
1225 prevhandler = None
1225 prevhandler = None
1226 if hasattr(signal, 'SIGCHLD'):
1226 if hasattr(signal, 'SIGCHLD'):
1227 prevhandler = signal.signal(signal.SIGCHLD, handler)
1227 prevhandler = signal.signal(signal.SIGCHLD, handler)
1228 try:
1228 try:
1229 pid = spawndetached(args)
1229 pid = spawndetached(args)
1230 while not condfn():
1230 while not condfn():
1231 if ((pid in terminated or not testpid(pid))
1231 if ((pid in terminated or not testpid(pid))
1232 and not condfn()):
1232 and not condfn()):
1233 return -1
1233 return -1
1234 time.sleep(0.1)
1234 time.sleep(0.1)
1235 return pid
1235 return pid
1236 finally:
1236 finally:
1237 if prevhandler is not None:
1237 if prevhandler is not None:
1238 signal.signal(signal.SIGCHLD, prevhandler)
1238 signal.signal(signal.SIGCHLD, prevhandler)
1239
1239
1240 try:
1240 try:
1241 any, all = any, all
1241 any, all = any, all
1242 except NameError:
1242 except NameError:
1243 def any(iterable):
1243 def any(iterable):
1244 for i in iterable:
1244 for i in iterable:
1245 if i:
1245 if i:
1246 return True
1246 return True
1247 return False
1247 return False
1248
1248
1249 def all(iterable):
1249 def all(iterable):
1250 for i in iterable:
1250 for i in iterable:
1251 if not i:
1251 if not i:
1252 return False
1252 return False
1253 return True
1253 return True
1254
1254
1255 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1255 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1256 """Return the result of interpolating items in the mapping into string s.
1256 """Return the result of interpolating items in the mapping into string s.
1257
1257
1258 prefix is a single character string, or a two character string with
1258 prefix is a single character string, or a two character string with
1259 a backslash as the first character if the prefix needs to be escaped in
1259 a backslash as the first character if the prefix needs to be escaped in
1260 a regular expression.
1260 a regular expression.
1261
1261
1262 fn is an optional function that will be applied to the replacement text
1262 fn is an optional function that will be applied to the replacement text
1263 just before replacement.
1263 just before replacement.
1264
1264
1265 escape_prefix is an optional flag that allows using doubled prefix for
1265 escape_prefix is an optional flag that allows using doubled prefix for
1266 its escaping.
1266 its escaping.
1267 """
1267 """
1268 fn = fn or (lambda s: s)
1268 fn = fn or (lambda s: s)
1269 patterns = '|'.join(mapping.keys())
1269 patterns = '|'.join(mapping.keys())
1270 if escape_prefix:
1270 if escape_prefix:
1271 patterns += '|' + prefix
1271 patterns += '|' + prefix
1272 if len(prefix) > 1:
1272 if len(prefix) > 1:
1273 prefix_char = prefix[1:]
1273 prefix_char = prefix[1:]
1274 else:
1274 else:
1275 prefix_char = prefix
1275 prefix_char = prefix
1276 mapping[prefix_char] = prefix_char
1276 mapping[prefix_char] = prefix_char
1277 r = re.compile(r'%s(%s)' % (prefix, patterns))
1277 r = re.compile(r'%s(%s)' % (prefix, patterns))
1278 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1278 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1279
1279
1280 def getport(port):
1280 def getport(port):
1281 """Return the port for a given network service.
1281 """Return the port for a given network service.
1282
1282
1283 If port is an integer, it's returned as is. If it's a string, it's
1283 If port is an integer, it's returned as is. If it's a string, it's
1284 looked up using socket.getservbyname(). If there's no matching
1284 looked up using socket.getservbyname(). If there's no matching
1285 service, util.Abort is raised.
1285 service, util.Abort is raised.
1286 """
1286 """
1287 try:
1287 try:
1288 return int(port)
1288 return int(port)
1289 except ValueError:
1289 except ValueError:
1290 pass
1290 pass
1291
1291
1292 try:
1292 try:
1293 return socket.getservbyname(port)
1293 return socket.getservbyname(port)
1294 except socket.error:
1294 except socket.error:
1295 raise Abort(_("no port number associated with service '%s'") % port)
1295 raise Abort(_("no port number associated with service '%s'") % port)
1296
1296
1297 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1297 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1298 '0': False, 'no': False, 'false': False, 'off': False,
1298 '0': False, 'no': False, 'false': False, 'off': False,
1299 'never': False}
1299 'never': False}
1300
1300
1301 def parsebool(s):
1301 def parsebool(s):
1302 """Parse s into a boolean.
1302 """Parse s into a boolean.
1303
1303
1304 If s is not a valid boolean, returns None.
1304 If s is not a valid boolean, returns None.
1305 """
1305 """
1306 return _booleans.get(s.lower(), None)
1306 return _booleans.get(s.lower(), None)
1307
1307
1308 _hexdig = '0123456789ABCDEFabcdef'
1308 _hexdig = '0123456789ABCDEFabcdef'
1309 _hextochr = dict((a + b, chr(int(a + b, 16)))
1309 _hextochr = dict((a + b, chr(int(a + b, 16)))
1310 for a in _hexdig for b in _hexdig)
1310 for a in _hexdig for b in _hexdig)
1311
1311
1312 def _urlunquote(s):
1312 def _urlunquote(s):
1313 """unquote('abc%20def') -> 'abc def'."""
1313 """unquote('abc%20def') -> 'abc def'."""
1314 res = s.split('%')
1314 res = s.split('%')
1315 # fastpath
1315 # fastpath
1316 if len(res) == 1:
1316 if len(res) == 1:
1317 return s
1317 return s
1318 s = res[0]
1318 s = res[0]
1319 for item in res[1:]:
1319 for item in res[1:]:
1320 try:
1320 try:
1321 s += _hextochr[item[:2]] + item[2:]
1321 s += _hextochr[item[:2]] + item[2:]
1322 except KeyError:
1322 except KeyError:
1323 s += '%' + item
1323 s += '%' + item
1324 except UnicodeDecodeError:
1324 except UnicodeDecodeError:
1325 s += unichr(int(item[:2], 16)) + item[2:]
1325 s += unichr(int(item[:2], 16)) + item[2:]
1326 return s
1326 return s
1327
1327
1328 class url(object):
1328 class url(object):
1329 r"""Reliable URL parser.
1329 r"""Reliable URL parser.
1330
1330
1331 This parses URLs and provides attributes for the following
1331 This parses URLs and provides attributes for the following
1332 components:
1332 components:
1333
1333
1334 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1334 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1335
1335
1336 Missing components are set to None. The only exception is
1336 Missing components are set to None. The only exception is
1337 fragment, which is set to '' if present but empty.
1337 fragment, which is set to '' if present but empty.
1338
1338
1339 If parsefragment is False, fragment is included in query. If
1339 If parsefragment is False, fragment is included in query. If
1340 parsequery is False, query is included in path. If both are
1340 parsequery is False, query is included in path. If both are
1341 False, both fragment and query are included in path.
1341 False, both fragment and query are included in path.
1342
1342
1343 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1343 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1344
1344
1345 Note that for backward compatibility reasons, bundle URLs do not
1345 Note that for backward compatibility reasons, bundle URLs do not
1346 take host names. That means 'bundle://../' has a path of '../'.
1346 take host names. That means 'bundle://../' has a path of '../'.
1347
1347
1348 Examples:
1348 Examples:
1349
1349
1350 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1350 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1351 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1351 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1352 >>> url('ssh://[::1]:2200//home/joe/repo')
1352 >>> url('ssh://[::1]:2200//home/joe/repo')
1353 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1353 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1354 >>> url('file:///home/joe/repo')
1354 >>> url('file:///home/joe/repo')
1355 <url scheme: 'file', path: '/home/joe/repo'>
1355 <url scheme: 'file', path: '/home/joe/repo'>
1356 >>> url('bundle:foo')
1356 >>> url('bundle:foo')
1357 <url scheme: 'bundle', path: 'foo'>
1357 <url scheme: 'bundle', path: 'foo'>
1358 >>> url('bundle://../foo')
1358 >>> url('bundle://../foo')
1359 <url scheme: 'bundle', path: '../foo'>
1359 <url scheme: 'bundle', path: '../foo'>
1360 >>> url(r'c:\foo\bar')
1360 >>> url(r'c:\foo\bar')
1361 <url path: 'c:\\foo\\bar'>
1361 <url path: 'c:\\foo\\bar'>
1362
1362
1363 Authentication credentials:
1363 Authentication credentials:
1364
1364
1365 >>> url('ssh://joe:xyz@x/repo')
1365 >>> url('ssh://joe:xyz@x/repo')
1366 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1366 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1367 >>> url('ssh://joe@x/repo')
1367 >>> url('ssh://joe@x/repo')
1368 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1368 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1369
1369
1370 Query strings and fragments:
1370 Query strings and fragments:
1371
1371
1372 >>> url('http://host/a?b#c')
1372 >>> url('http://host/a?b#c')
1373 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1373 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1374 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1374 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1375 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1375 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1376 """
1376 """
1377
1377
1378 _safechars = "!~*'()+"
1378 _safechars = "!~*'()+"
1379 _safepchars = "/!~*'()+"
1379 _safepchars = "/!~*'()+"
1380 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1380 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1381
1381
1382 def __init__(self, path, parsequery=True, parsefragment=True):
1382 def __init__(self, path, parsequery=True, parsefragment=True):
1383 # We slowly chomp away at path until we have only the path left
1383 # We slowly chomp away at path until we have only the path left
1384 self.scheme = self.user = self.passwd = self.host = None
1384 self.scheme = self.user = self.passwd = self.host = None
1385 self.port = self.path = self.query = self.fragment = None
1385 self.port = self.path = self.query = self.fragment = None
1386 self._localpath = True
1386 self._localpath = True
1387 self._hostport = ''
1387 self._hostport = ''
1388 self._origpath = path
1388 self._origpath = path
1389
1389
1390 # special case for Windows drive letters
1390 # special case for Windows drive letters
1391 if hasdriveletter(path):
1391 if hasdriveletter(path):
1392 self.path = path
1392 self.path = path
1393 return
1393 return
1394
1394
1395 # For compatibility reasons, we can't handle bundle paths as
1395 # For compatibility reasons, we can't handle bundle paths as
1396 # normal URLS
1396 # normal URLS
1397 if path.startswith('bundle:'):
1397 if path.startswith('bundle:'):
1398 self.scheme = 'bundle'
1398 self.scheme = 'bundle'
1399 path = path[7:]
1399 path = path[7:]
1400 if path.startswith('//'):
1400 if path.startswith('//'):
1401 path = path[2:]
1401 path = path[2:]
1402 self.path = path
1402 self.path = path
1403 return
1403 return
1404
1404
1405 if self._matchscheme(path):
1405 if self._matchscheme(path):
1406 parts = path.split(':', 1)
1406 parts = path.split(':', 1)
1407 if parts[0]:
1407 if parts[0]:
1408 self.scheme, path = parts
1408 self.scheme, path = parts
1409 self._localpath = False
1409 self._localpath = False
1410
1410
1411 if not path:
1411 if not path:
1412 path = None
1412 path = None
1413 if self._localpath:
1413 if self._localpath:
1414 self.path = ''
1414 self.path = ''
1415 return
1415 return
1416 else:
1416 else:
1417 if parsefragment and '#' in path:
1417 if parsefragment and '#' in path:
1418 path, self.fragment = path.split('#', 1)
1418 path, self.fragment = path.split('#', 1)
1419 if not path:
1419 if not path:
1420 path = None
1420 path = None
1421 if self._localpath:
1421 if self._localpath:
1422 self.path = path
1422 self.path = path
1423 return
1423 return
1424
1424
1425 if parsequery and '?' in path:
1425 if parsequery and '?' in path:
1426 path, self.query = path.split('?', 1)
1426 path, self.query = path.split('?', 1)
1427 if not path:
1427 if not path:
1428 path = None
1428 path = None
1429 if not self.query:
1429 if not self.query:
1430 self.query = None
1430 self.query = None
1431
1431
1432 # // is required to specify a host/authority
1432 # // is required to specify a host/authority
1433 if path and path.startswith('//'):
1433 if path and path.startswith('//'):
1434 parts = path[2:].split('/', 1)
1434 parts = path[2:].split('/', 1)
1435 if len(parts) > 1:
1435 if len(parts) > 1:
1436 self.host, path = parts
1436 self.host, path = parts
1437 path = path
1437 path = path
1438 else:
1438 else:
1439 self.host = parts[0]
1439 self.host = parts[0]
1440 path = None
1440 path = None
1441 if not self.host:
1441 if not self.host:
1442 self.host = None
1442 self.host = None
1443 if path:
1443 if path:
1444 path = '/' + path
1444 path = '/' + path
1445
1445
1446 if self.host and '@' in self.host:
1446 if self.host and '@' in self.host:
1447 self.user, self.host = self.host.rsplit('@', 1)
1447 self.user, self.host = self.host.rsplit('@', 1)
1448 if ':' in self.user:
1448 if ':' in self.user:
1449 self.user, self.passwd = self.user.split(':', 1)
1449 self.user, self.passwd = self.user.split(':', 1)
1450 if not self.host:
1450 if not self.host:
1451 self.host = None
1451 self.host = None
1452
1452
1453 # Don't split on colons in IPv6 addresses without ports
1453 # Don't split on colons in IPv6 addresses without ports
1454 if (self.host and ':' in self.host and
1454 if (self.host and ':' in self.host and
1455 not (self.host.startswith('[') and self.host.endswith(']'))):
1455 not (self.host.startswith('[') and self.host.endswith(']'))):
1456 self._hostport = self.host
1456 self._hostport = self.host
1457 self.host, self.port = self.host.rsplit(':', 1)
1457 self.host, self.port = self.host.rsplit(':', 1)
1458 if not self.host:
1458 if not self.host:
1459 self.host = None
1459 self.host = None
1460
1460
1461 if (self.host and self.scheme == 'file' and
1461 if (self.host and self.scheme == 'file' and
1462 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1462 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1463 raise Abort(_('file:// URLs can only refer to localhost'))
1463 raise Abort(_('file:// URLs can only refer to localhost'))
1464
1464
1465 self.path = path
1465 self.path = path
1466
1466
1467 for a in ('user', 'passwd', 'host', 'port',
1467 for a in ('user', 'passwd', 'host', 'port',
1468 'path', 'query', 'fragment'):
1468 'path', 'query', 'fragment'):
1469 v = getattr(self, a)
1469 v = getattr(self, a)
1470 if v is not None:
1470 if v is not None:
1471 setattr(self, a, _urlunquote(v))
1471 setattr(self, a, _urlunquote(v))
1472
1472
1473 def __repr__(self):
1473 def __repr__(self):
1474 attrs = []
1474 attrs = []
1475 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1475 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1476 'query', 'fragment'):
1476 'query', 'fragment'):
1477 v = getattr(self, a)
1477 v = getattr(self, a)
1478 if v is not None:
1478 if v is not None:
1479 attrs.append('%s: %r' % (a, v))
1479 attrs.append('%s: %r' % (a, v))
1480 return '<url %s>' % ', '.join(attrs)
1480 return '<url %s>' % ', '.join(attrs)
1481
1481
1482 def __str__(self):
1482 def __str__(self):
1483 r"""Join the URL's components back into a URL string.
1483 r"""Join the URL's components back into a URL string.
1484
1484
1485 Examples:
1485 Examples:
1486
1486
1487 >>> str(url('http://user:pw@host:80/?foo#bar'))
1487 >>> str(url('http://user:pw@host:80/?foo#bar'))
1488 'http://user:pw@host:80/?foo#bar'
1488 'http://user:pw@host:80/?foo#bar'
1489 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1489 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1490 'ssh://user:pw@[::1]:2200//home/joe#'
1490 'ssh://user:pw@[::1]:2200//home/joe#'
1491 >>> str(url('http://localhost:80//'))
1491 >>> str(url('http://localhost:80//'))
1492 'http://localhost:80//'
1492 'http://localhost:80//'
1493 >>> str(url('http://localhost:80/'))
1493 >>> str(url('http://localhost:80/'))
1494 'http://localhost:80/'
1494 'http://localhost:80/'
1495 >>> str(url('http://localhost:80'))
1495 >>> str(url('http://localhost:80'))
1496 'http://localhost:80/'
1496 'http://localhost:80/'
1497 >>> str(url('bundle:foo'))
1497 >>> str(url('bundle:foo'))
1498 'bundle:foo'
1498 'bundle:foo'
1499 >>> str(url('bundle://../foo'))
1499 >>> str(url('bundle://../foo'))
1500 'bundle:../foo'
1500 'bundle:../foo'
1501 >>> str(url('path'))
1501 >>> str(url('path'))
1502 'path'
1502 'path'
1503 >>> print url(r'bundle:foo\bar')
1503 >>> print url(r'bundle:foo\bar')
1504 bundle:foo\bar
1504 bundle:foo\bar
1505 """
1505 """
1506 if self._localpath:
1506 if self._localpath:
1507 s = self.path
1507 s = self.path
1508 if self.scheme == 'bundle':
1508 if self.scheme == 'bundle':
1509 s = 'bundle:' + s
1509 s = 'bundle:' + s
1510 if self.fragment:
1510 if self.fragment:
1511 s += '#' + self.fragment
1511 s += '#' + self.fragment
1512 return s
1512 return s
1513
1513
1514 s = self.scheme + ':'
1514 s = self.scheme + ':'
1515 if (self.user or self.passwd or self.host or
1515 if (self.user or self.passwd or self.host or
1516 self.scheme and not self.path):
1516 self.scheme and not self.path):
1517 s += '//'
1517 s += '//'
1518 if self.user:
1518 if self.user:
1519 s += urllib.quote(self.user, safe=self._safechars)
1519 s += urllib.quote(self.user, safe=self._safechars)
1520 if self.passwd:
1520 if self.passwd:
1521 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1521 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1522 if self.user or self.passwd:
1522 if self.user or self.passwd:
1523 s += '@'
1523 s += '@'
1524 if self.host:
1524 if self.host:
1525 if not (self.host.startswith('[') and self.host.endswith(']')):
1525 if not (self.host.startswith('[') and self.host.endswith(']')):
1526 s += urllib.quote(self.host)
1526 s += urllib.quote(self.host)
1527 else:
1527 else:
1528 s += self.host
1528 s += self.host
1529 if self.port:
1529 if self.port:
1530 s += ':' + urllib.quote(self.port)
1530 s += ':' + urllib.quote(self.port)
1531 if self.host:
1531 if self.host:
1532 s += '/'
1532 s += '/'
1533 if self.path:
1533 if self.path:
1534 s += urllib.quote(self.path, safe=self._safepchars)
1534 s += urllib.quote(self.path, safe=self._safepchars)
1535 if self.query:
1535 if self.query:
1536 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1536 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1537 if self.fragment is not None:
1537 if self.fragment is not None:
1538 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1538 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1539 return s
1539 return s
1540
1540
1541 def authinfo(self):
1541 def authinfo(self):
1542 user, passwd = self.user, self.passwd
1542 user, passwd = self.user, self.passwd
1543 try:
1543 try:
1544 self.user, self.passwd = None, None
1544 self.user, self.passwd = None, None
1545 s = str(self)
1545 s = str(self)
1546 finally:
1546 finally:
1547 self.user, self.passwd = user, passwd
1547 self.user, self.passwd = user, passwd
1548 if not self.user:
1548 if not self.user:
1549 return (s, None)
1549 return (s, None)
1550 return (s, (None, (str(self), self.host),
1550 return (s, (None, (str(self), self.host),
1551 self.user, self.passwd or ''))
1551 self.user, self.passwd or ''))
1552
1552
1553 def localpath(self):
1553 def localpath(self):
1554 if self.scheme == 'file' or self.scheme == 'bundle':
1554 if self.scheme == 'file' or self.scheme == 'bundle':
1555 path = self.path or '/'
1555 path = self.path or '/'
1556 # For Windows, we need to promote hosts containing drive
1556 # For Windows, we need to promote hosts containing drive
1557 # letters to paths with drive letters.
1557 # letters to paths with drive letters.
1558 if hasdriveletter(self._hostport):
1558 if hasdriveletter(self._hostport):
1559 path = self._hostport + '/' + self.path
1559 path = self._hostport + '/' + self.path
1560 elif self.host is not None and self.path:
1560 elif self.host is not None and self.path:
1561 path = '/' + path
1561 path = '/' + path
1562 # We also need to handle the case of file:///C:/, which
1562 # We also need to handle the case of file:///C:/, which
1563 # should return C:/, not /C:/.
1563 # should return C:/, not /C:/.
1564 elif hasdriveletter(path):
1564 elif hasdriveletter(path):
1565 # Strip leading slash from paths with drive names
1565 # Strip leading slash from paths with drive names
1566 return path[1:]
1566 return path[1:]
1567 return path
1567 return path
1568 return self._origpath
1568 return self._origpath
1569
1569
1570 def hasscheme(path):
1570 def hasscheme(path):
1571 return bool(url(path).scheme)
1571 return bool(url(path).scheme)
1572
1572
1573 def hasdriveletter(path):
1573 def hasdriveletter(path):
1574 return path[1:2] == ':' and path[0:1].isalpha()
1574 return path[1:2] == ':' and path[0:1].isalpha()
1575
1575
1576 def localpath(path):
1576 def localpath(path):
1577 return url(path, parsequery=False, parsefragment=False).localpath()
1577 return url(path, parsequery=False, parsefragment=False).localpath()
1578
1578
1579 def hidepassword(u):
1579 def hidepassword(u):
1580 '''hide user credential in a url string'''
1580 '''hide user credential in a url string'''
1581 u = url(u)
1581 u = url(u)
1582 if u.passwd:
1582 if u.passwd:
1583 u.passwd = '***'
1583 u.passwd = '***'
1584 return str(u)
1584 return str(u)
1585
1585
1586 def removeauth(u):
1586 def removeauth(u):
1587 '''remove all authentication information from a url string'''
1587 '''remove all authentication information from a url string'''
1588 u = url(u)
1588 u = url(u)
1589 u.user = u.passwd = None
1589 u.user = u.passwd = None
1590 return str(u)
1590 return str(u)
@@ -1,376 +1,376
1 # win32.py - utility functions that use win32 API
1 # win32.py - utility functions that use win32 API
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import encoding
8 import encoding
9 import ctypes, errno, os, struct, subprocess, random
9 import ctypes, errno, os, struct, subprocess, random
10
10
11 _kernel32 = ctypes.windll.kernel32
11 _kernel32 = ctypes.windll.kernel32
12
12
13 _BOOL = ctypes.c_long
13 _BOOL = ctypes.c_long
14 _WORD = ctypes.c_ushort
14 _WORD = ctypes.c_ushort
15 _DWORD = ctypes.c_ulong
15 _DWORD = ctypes.c_ulong
16 _LPCSTR = _LPSTR = ctypes.c_char_p
16 _LPCSTR = _LPSTR = ctypes.c_char_p
17 _HANDLE = ctypes.c_void_p
17 _HANDLE = ctypes.c_void_p
18 _HWND = _HANDLE
18 _HWND = _HANDLE
19
19
20 _INVALID_HANDLE_VALUE = -1
20 _INVALID_HANDLE_VALUE = -1
21
21
22 # GetLastError
22 # GetLastError
23 _ERROR_SUCCESS = 0
23 _ERROR_SUCCESS = 0
24 _ERROR_INVALID_PARAMETER = 87
24 _ERROR_INVALID_PARAMETER = 87
25 _ERROR_INSUFFICIENT_BUFFER = 122
25 _ERROR_INSUFFICIENT_BUFFER = 122
26
26
27 # WPARAM is defined as UINT_PTR (unsigned type)
27 # WPARAM is defined as UINT_PTR (unsigned type)
28 # LPARAM is defined as LONG_PTR (signed type)
28 # LPARAM is defined as LONG_PTR (signed type)
29 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
29 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
30 _WPARAM = ctypes.c_ulong
30 _WPARAM = ctypes.c_ulong
31 _LPARAM = ctypes.c_long
31 _LPARAM = ctypes.c_long
32 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
32 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
33 _WPARAM = ctypes.c_ulonglong
33 _WPARAM = ctypes.c_ulonglong
34 _LPARAM = ctypes.c_longlong
34 _LPARAM = ctypes.c_longlong
35
35
36 class _FILETIME(ctypes.Structure):
36 class _FILETIME(ctypes.Structure):
37 _fields_ = [('dwLowDateTime', _DWORD),
37 _fields_ = [('dwLowDateTime', _DWORD),
38 ('dwHighDateTime', _DWORD)]
38 ('dwHighDateTime', _DWORD)]
39
39
40 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
40 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
41 _fields_ = [('dwFileAttributes', _DWORD),
41 _fields_ = [('dwFileAttributes', _DWORD),
42 ('ftCreationTime', _FILETIME),
42 ('ftCreationTime', _FILETIME),
43 ('ftLastAccessTime', _FILETIME),
43 ('ftLastAccessTime', _FILETIME),
44 ('ftLastWriteTime', _FILETIME),
44 ('ftLastWriteTime', _FILETIME),
45 ('dwVolumeSerialNumber', _DWORD),
45 ('dwVolumeSerialNumber', _DWORD),
46 ('nFileSizeHigh', _DWORD),
46 ('nFileSizeHigh', _DWORD),
47 ('nFileSizeLow', _DWORD),
47 ('nFileSizeLow', _DWORD),
48 ('nNumberOfLinks', _DWORD),
48 ('nNumberOfLinks', _DWORD),
49 ('nFileIndexHigh', _DWORD),
49 ('nFileIndexHigh', _DWORD),
50 ('nFileIndexLow', _DWORD)]
50 ('nFileIndexLow', _DWORD)]
51
51
52 # CreateFile
52 # CreateFile
53 _FILE_SHARE_READ = 0x00000001
53 _FILE_SHARE_READ = 0x00000001
54 _FILE_SHARE_WRITE = 0x00000002
54 _FILE_SHARE_WRITE = 0x00000002
55 _FILE_SHARE_DELETE = 0x00000004
55 _FILE_SHARE_DELETE = 0x00000004
56
56
57 _OPEN_EXISTING = 3
57 _OPEN_EXISTING = 3
58
58
59 # SetFileAttributes
59 # SetFileAttributes
60 _FILE_ATTRIBUTE_NORMAL = 0x80
60 _FILE_ATTRIBUTE_NORMAL = 0x80
61 _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
61 _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000
62
62
63 # Process Security and Access Rights
63 # Process Security and Access Rights
64 _PROCESS_QUERY_INFORMATION = 0x0400
64 _PROCESS_QUERY_INFORMATION = 0x0400
65
65
66 # GetExitCodeProcess
66 # GetExitCodeProcess
67 _STILL_ACTIVE = 259
67 _STILL_ACTIVE = 259
68
68
69 # registry
69 # registry
70 _HKEY_CURRENT_USER = 0x80000001L
70 _HKEY_CURRENT_USER = 0x80000001L
71 _HKEY_LOCAL_MACHINE = 0x80000002L
71 _HKEY_LOCAL_MACHINE = 0x80000002L
72 _KEY_READ = 0x20019
72 _KEY_READ = 0x20019
73 _REG_SZ = 1
73 _REG_SZ = 1
74 _REG_DWORD = 4
74 _REG_DWORD = 4
75
75
76 class _STARTUPINFO(ctypes.Structure):
76 class _STARTUPINFO(ctypes.Structure):
77 _fields_ = [('cb', _DWORD),
77 _fields_ = [('cb', _DWORD),
78 ('lpReserved', _LPSTR),
78 ('lpReserved', _LPSTR),
79 ('lpDesktop', _LPSTR),
79 ('lpDesktop', _LPSTR),
80 ('lpTitle', _LPSTR),
80 ('lpTitle', _LPSTR),
81 ('dwX', _DWORD),
81 ('dwX', _DWORD),
82 ('dwY', _DWORD),
82 ('dwY', _DWORD),
83 ('dwXSize', _DWORD),
83 ('dwXSize', _DWORD),
84 ('dwYSize', _DWORD),
84 ('dwYSize', _DWORD),
85 ('dwXCountChars', _DWORD),
85 ('dwXCountChars', _DWORD),
86 ('dwYCountChars', _DWORD),
86 ('dwYCountChars', _DWORD),
87 ('dwFillAttribute', _DWORD),
87 ('dwFillAttribute', _DWORD),
88 ('dwFlags', _DWORD),
88 ('dwFlags', _DWORD),
89 ('wShowWindow', _WORD),
89 ('wShowWindow', _WORD),
90 ('cbReserved2', _WORD),
90 ('cbReserved2', _WORD),
91 ('lpReserved2', ctypes.c_char_p),
91 ('lpReserved2', ctypes.c_char_p),
92 ('hStdInput', _HANDLE),
92 ('hStdInput', _HANDLE),
93 ('hStdOutput', _HANDLE),
93 ('hStdOutput', _HANDLE),
94 ('hStdError', _HANDLE)]
94 ('hStdError', _HANDLE)]
95
95
96 class _PROCESS_INFORMATION(ctypes.Structure):
96 class _PROCESS_INFORMATION(ctypes.Structure):
97 _fields_ = [('hProcess', _HANDLE),
97 _fields_ = [('hProcess', _HANDLE),
98 ('hThread', _HANDLE),
98 ('hThread', _HANDLE),
99 ('dwProcessId', _DWORD),
99 ('dwProcessId', _DWORD),
100 ('dwThreadId', _DWORD)]
100 ('dwThreadId', _DWORD)]
101
101
102 _DETACHED_PROCESS = 0x00000008
102 _DETACHED_PROCESS = 0x00000008
103 _STARTF_USESHOWWINDOW = 0x00000001
103 _STARTF_USESHOWWINDOW = 0x00000001
104 _SW_HIDE = 0
104 _SW_HIDE = 0
105
105
106 class _COORD(ctypes.Structure):
106 class _COORD(ctypes.Structure):
107 _fields_ = [('X', ctypes.c_short),
107 _fields_ = [('X', ctypes.c_short),
108 ('Y', ctypes.c_short)]
108 ('Y', ctypes.c_short)]
109
109
110 class _SMALL_RECT(ctypes.Structure):
110 class _SMALL_RECT(ctypes.Structure):
111 _fields_ = [('Left', ctypes.c_short),
111 _fields_ = [('Left', ctypes.c_short),
112 ('Top', ctypes.c_short),
112 ('Top', ctypes.c_short),
113 ('Right', ctypes.c_short),
113 ('Right', ctypes.c_short),
114 ('Bottom', ctypes.c_short)]
114 ('Bottom', ctypes.c_short)]
115
115
116 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
116 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
117 _fields_ = [('dwSize', _COORD),
117 _fields_ = [('dwSize', _COORD),
118 ('dwCursorPosition', _COORD),
118 ('dwCursorPosition', _COORD),
119 ('wAttributes', _WORD),
119 ('wAttributes', _WORD),
120 ('srWindow', _SMALL_RECT),
120 ('srWindow', _SMALL_RECT),
121 ('dwMaximumWindowSize', _COORD)]
121 ('dwMaximumWindowSize', _COORD)]
122
122
123 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
123 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
124
124
125 def _raiseoserror(name):
125 def _raiseoserror(name):
126 err = ctypes.WinError()
126 err = ctypes.WinError()
127 raise OSError(err.errno, '%s: %s' % (name, err.strerror))
127 raise OSError(err.errno, '%s: %s' % (name, err.strerror))
128
128
129 def _getfileinfo(name):
129 def _getfileinfo(name):
130 fh = _kernel32.CreateFileA(name, 0,
130 fh = _kernel32.CreateFileA(name, 0,
131 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
131 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
132 None, _OPEN_EXISTING, 0, None)
132 None, _OPEN_EXISTING, 0, None)
133 if fh == _INVALID_HANDLE_VALUE:
133 if fh == _INVALID_HANDLE_VALUE:
134 _raiseoserror(name)
134 _raiseoserror(name)
135 try:
135 try:
136 fi = _BY_HANDLE_FILE_INFORMATION()
136 fi = _BY_HANDLE_FILE_INFORMATION()
137 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
137 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
138 _raiseoserror(name)
138 _raiseoserror(name)
139 return fi
139 return fi
140 finally:
140 finally:
141 _kernel32.CloseHandle(fh)
141 _kernel32.CloseHandle(fh)
142
142
143 def os_link(src, dst):
143 def os_link(src, dst):
144 try:
144 try:
145 if not _kernel32.CreateHardLinkA(dst, src, None):
145 if not _kernel32.CreateHardLinkA(dst, src, None):
146 _raiseoserror(src)
146 _raiseoserror(src)
147 except AttributeError: # Wine doesn't support this function
147 except AttributeError: # Wine doesn't support this function
148 _raiseoserror(src)
148 _raiseoserror(src)
149
149
150 def nlinks(name):
150 def nlinks(name):
151 '''return number of hardlinks for the given file'''
151 '''return number of hardlinks for the given file'''
152 return _getfileinfo(name).nNumberOfLinks
152 return _getfileinfo(name).nNumberOfLinks
153
153
154 def samefile(fpath1, fpath2):
154 def samefile(fpath1, fpath2):
155 '''Returns whether fpath1 and fpath2 refer to the same file. This is only
155 '''Returns whether fpath1 and fpath2 refer to the same file. This is only
156 guaranteed to work for files, not directories.'''
156 guaranteed to work for files, not directories.'''
157 res1 = _getfileinfo(fpath1)
157 res1 = _getfileinfo(fpath1)
158 res2 = _getfileinfo(fpath2)
158 res2 = _getfileinfo(fpath2)
159 return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
159 return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
160 and res1.nFileIndexHigh == res2.nFileIndexHigh
160 and res1.nFileIndexHigh == res2.nFileIndexHigh
161 and res1.nFileIndexLow == res2.nFileIndexLow)
161 and res1.nFileIndexLow == res2.nFileIndexLow)
162
162
163 def samedevice(fpath1, fpath2):
163 def samedevice(fpath1, fpath2):
164 '''Returns whether fpath1 and fpath2 are on the same device. This is only
164 '''Returns whether fpath1 and fpath2 are on the same device. This is only
165 guaranteed to work for files, not directories.'''
165 guaranteed to work for files, not directories.'''
166 res1 = _getfileinfo(fpath1)
166 res1 = _getfileinfo(fpath1)
167 res2 = _getfileinfo(fpath2)
167 res2 = _getfileinfo(fpath2)
168 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
168 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
169
169
170 def testpid(pid):
170 def testpid(pid):
171 '''return True if pid is still running or unable to
171 '''return True if pid is still running or unable to
172 determine, False otherwise'''
172 determine, False otherwise'''
173 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
173 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
174 if h:
174 if h:
175 try:
175 try:
176 status = _DWORD()
176 status = _DWORD()
177 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
177 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
178 return status.value == _STILL_ACTIVE
178 return status.value == _STILL_ACTIVE
179 finally:
179 finally:
180 _kernel32.CloseHandle(h)
180 _kernel32.CloseHandle(h)
181 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
181 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
182
182
183 def lookup_reg(key, valname=None, scope=None):
183 def lookupreg(key, valname=None, scope=None):
184 ''' Look up a key/value name in the Windows registry.
184 ''' Look up a key/value name in the Windows registry.
185
185
186 valname: value name. If unspecified, the default value for the key
186 valname: value name. If unspecified, the default value for the key
187 is used.
187 is used.
188 scope: optionally specify scope for registry lookup, this can be
188 scope: optionally specify scope for registry lookup, this can be
189 a sequence of scopes to look up in order. Default (CURRENT_USER,
189 a sequence of scopes to look up in order. Default (CURRENT_USER,
190 LOCAL_MACHINE).
190 LOCAL_MACHINE).
191 '''
191 '''
192 adv = ctypes.windll.advapi32
192 adv = ctypes.windll.advapi32
193 byref = ctypes.byref
193 byref = ctypes.byref
194 if scope is None:
194 if scope is None:
195 scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE)
195 scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE)
196 elif not isinstance(scope, (list, tuple)):
196 elif not isinstance(scope, (list, tuple)):
197 scope = (scope,)
197 scope = (scope,)
198 for s in scope:
198 for s in scope:
199 kh = _HANDLE()
199 kh = _HANDLE()
200 res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh))
200 res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh))
201 if res != _ERROR_SUCCESS:
201 if res != _ERROR_SUCCESS:
202 continue
202 continue
203 try:
203 try:
204 size = _DWORD(600)
204 size = _DWORD(600)
205 type = _DWORD()
205 type = _DWORD()
206 buf = ctypes.create_string_buffer(size.value + 1)
206 buf = ctypes.create_string_buffer(size.value + 1)
207 res = adv.RegQueryValueExA(kh.value, valname, None,
207 res = adv.RegQueryValueExA(kh.value, valname, None,
208 byref(type), buf, byref(size))
208 byref(type), buf, byref(size))
209 if res != _ERROR_SUCCESS:
209 if res != _ERROR_SUCCESS:
210 continue
210 continue
211 if type.value == _REG_SZ:
211 if type.value == _REG_SZ:
212 # never let a Unicode string escape into the wild
212 # never let a Unicode string escape into the wild
213 return encoding.tolocal(buf.value.encode('UTF-8'))
213 return encoding.tolocal(buf.value.encode('UTF-8'))
214 elif type.value == _REG_DWORD:
214 elif type.value == _REG_DWORD:
215 fmt = '<L'
215 fmt = '<L'
216 s = ctypes.string_at(byref(buf), struct.calcsize(fmt))
216 s = ctypes.string_at(byref(buf), struct.calcsize(fmt))
217 return struct.unpack(fmt, s)[0]
217 return struct.unpack(fmt, s)[0]
218 finally:
218 finally:
219 adv.RegCloseKey(kh.value)
219 adv.RegCloseKey(kh.value)
220
220
221 def executable_path():
221 def executable_path():
222 '''return full path of hg.exe'''
222 '''return full path of hg.exe'''
223 size = 600
223 size = 600
224 buf = ctypes.create_string_buffer(size + 1)
224 buf = ctypes.create_string_buffer(size + 1)
225 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
225 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
226 if len == 0:
226 if len == 0:
227 raise ctypes.WinError()
227 raise ctypes.WinError()
228 elif len == size:
228 elif len == size:
229 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
229 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
230 return buf.value
230 return buf.value
231
231
232 def getuser():
232 def getuser():
233 '''return name of current user'''
233 '''return name of current user'''
234 adv = ctypes.windll.advapi32
234 adv = ctypes.windll.advapi32
235 size = _DWORD(300)
235 size = _DWORD(300)
236 buf = ctypes.create_string_buffer(size.value + 1)
236 buf = ctypes.create_string_buffer(size.value + 1)
237 if not adv.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
237 if not adv.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
238 raise ctypes.WinError()
238 raise ctypes.WinError()
239 return buf.value
239 return buf.value
240
240
241 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
241 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
242 _signal_handler = []
242 _signal_handler = []
243
243
244 def set_signal_handler():
244 def set_signal_handler():
245 '''Register a termination handler for console events including
245 '''Register a termination handler for console events including
246 CTRL+C. python signal handlers do not work well with socket
246 CTRL+C. python signal handlers do not work well with socket
247 operations.
247 operations.
248 '''
248 '''
249 def handler(event):
249 def handler(event):
250 _kernel32.ExitProcess(1)
250 _kernel32.ExitProcess(1)
251
251
252 if _signal_handler:
252 if _signal_handler:
253 return # already registered
253 return # already registered
254 h = _SIGNAL_HANDLER(handler)
254 h = _SIGNAL_HANDLER(handler)
255 _signal_handler.append(h) # needed to prevent garbage collection
255 _signal_handler.append(h) # needed to prevent garbage collection
256 if not _kernel32.SetConsoleCtrlHandler(h, True):
256 if not _kernel32.SetConsoleCtrlHandler(h, True):
257 raise ctypes.WinError()
257 raise ctypes.WinError()
258
258
259 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
259 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
260
260
261 def hidewindow():
261 def hidewindow():
262 user32 = ctypes.windll.user32
262 user32 = ctypes.windll.user32
263
263
264 def callback(hwnd, pid):
264 def callback(hwnd, pid):
265 wpid = _DWORD()
265 wpid = _DWORD()
266 user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
266 user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
267 if pid == wpid.value:
267 if pid == wpid.value:
268 user32.ShowWindow(hwnd, _SW_HIDE)
268 user32.ShowWindow(hwnd, _SW_HIDE)
269 return False # stop enumerating windows
269 return False # stop enumerating windows
270 return True
270 return True
271
271
272 pid = _kernel32.GetCurrentProcessId()
272 pid = _kernel32.GetCurrentProcessId()
273 user32.EnumWindows(_WNDENUMPROC(callback), pid)
273 user32.EnumWindows(_WNDENUMPROC(callback), pid)
274
274
275 def termwidth():
275 def termwidth():
276 # cmd.exe does not handle CR like a unix console, the CR is
276 # cmd.exe does not handle CR like a unix console, the CR is
277 # counted in the line length. On 80 columns consoles, if 80
277 # counted in the line length. On 80 columns consoles, if 80
278 # characters are written, the following CR won't apply on the
278 # characters are written, the following CR won't apply on the
279 # current line but on the new one. Keep room for it.
279 # current line but on the new one. Keep room for it.
280 width = 79
280 width = 79
281 # Query stderr to avoid problems with redirections
281 # Query stderr to avoid problems with redirections
282 screenbuf = _kernel32.GetStdHandle(
282 screenbuf = _kernel32.GetStdHandle(
283 _STD_ERROR_HANDLE) # don't close the handle returned
283 _STD_ERROR_HANDLE) # don't close the handle returned
284 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
284 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
285 return width
285 return width
286 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
286 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
287 if not _kernel32.GetConsoleScreenBufferInfo(
287 if not _kernel32.GetConsoleScreenBufferInfo(
288 screenbuf, ctypes.byref(csbi)):
288 screenbuf, ctypes.byref(csbi)):
289 return width
289 return width
290 width = csbi.srWindow.Right - csbi.srWindow.Left
290 width = csbi.srWindow.Right - csbi.srWindow.Left
291 return width
291 return width
292
292
293 def spawndetached(args):
293 def spawndetached(args):
294 # No standard library function really spawns a fully detached
294 # No standard library function really spawns a fully detached
295 # process under win32 because they allocate pipes or other objects
295 # process under win32 because they allocate pipes or other objects
296 # to handle standard streams communications. Passing these objects
296 # to handle standard streams communications. Passing these objects
297 # to the child process requires handle inheritance to be enabled
297 # to the child process requires handle inheritance to be enabled
298 # which makes really detached processes impossible.
298 # which makes really detached processes impossible.
299 si = _STARTUPINFO()
299 si = _STARTUPINFO()
300 si.cb = ctypes.sizeof(_STARTUPINFO)
300 si.cb = ctypes.sizeof(_STARTUPINFO)
301 si.dwFlags = _STARTF_USESHOWWINDOW
301 si.dwFlags = _STARTF_USESHOWWINDOW
302 si.wShowWindow = _SW_HIDE
302 si.wShowWindow = _SW_HIDE
303
303
304 pi = _PROCESS_INFORMATION()
304 pi = _PROCESS_INFORMATION()
305
305
306 env = ''
306 env = ''
307 for k in os.environ:
307 for k in os.environ:
308 env += "%s=%s\0" % (k, os.environ[k])
308 env += "%s=%s\0" % (k, os.environ[k])
309 if not env:
309 if not env:
310 env = '\0'
310 env = '\0'
311 env += '\0'
311 env += '\0'
312
312
313 args = subprocess.list2cmdline(args)
313 args = subprocess.list2cmdline(args)
314 # Not running the command in shell mode makes python26 hang when
314 # Not running the command in shell mode makes python26 hang when
315 # writing to hgweb output socket.
315 # writing to hgweb output socket.
316 comspec = os.environ.get("COMSPEC", "cmd.exe")
316 comspec = os.environ.get("COMSPEC", "cmd.exe")
317 args = comspec + " /c " + args
317 args = comspec + " /c " + args
318
318
319 res = _kernel32.CreateProcessA(
319 res = _kernel32.CreateProcessA(
320 None, args, None, None, False, _DETACHED_PROCESS,
320 None, args, None, None, False, _DETACHED_PROCESS,
321 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
321 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
322 if not res:
322 if not res:
323 raise ctypes.WinError()
323 raise ctypes.WinError()
324
324
325 return pi.dwProcessId
325 return pi.dwProcessId
326
326
327 def unlink(f):
327 def unlink(f):
328 '''try to implement POSIX' unlink semantics on Windows'''
328 '''try to implement POSIX' unlink semantics on Windows'''
329
329
330 # POSIX allows to unlink and rename open files. Windows has serious
330 # POSIX allows to unlink and rename open files. Windows has serious
331 # problems with doing that:
331 # problems with doing that:
332 # - Calling os.unlink (or os.rename) on a file f fails if f or any
332 # - Calling os.unlink (or os.rename) on a file f fails if f or any
333 # hardlinked copy of f has been opened with Python's open(). There is no
333 # hardlinked copy of f has been opened with Python's open(). There is no
334 # way such a file can be deleted or renamed on Windows (other than
334 # way such a file can be deleted or renamed on Windows (other than
335 # scheduling the delete or rename for the next reboot).
335 # scheduling the delete or rename for the next reboot).
336 # - Calling os.unlink on a file that has been opened with Mercurial's
336 # - Calling os.unlink on a file that has been opened with Mercurial's
337 # posixfile (or comparable methods) will delay the actual deletion of
337 # posixfile (or comparable methods) will delay the actual deletion of
338 # the file for as long as the file is held open. The filename is blocked
338 # the file for as long as the file is held open. The filename is blocked
339 # during that time and cannot be used for recreating a new file under
339 # during that time and cannot be used for recreating a new file under
340 # that same name ("zombie file"). Directories containing such zombie files
340 # that same name ("zombie file"). Directories containing such zombie files
341 # cannot be removed or moved.
341 # cannot be removed or moved.
342 # A file that has been opened with posixfile can be renamed, so we rename
342 # A file that has been opened with posixfile can be renamed, so we rename
343 # f to a random temporary name before calling os.unlink on it. This allows
343 # f to a random temporary name before calling os.unlink on it. This allows
344 # callers to recreate f immediately while having other readers do their
344 # callers to recreate f immediately while having other readers do their
345 # implicit zombie filename blocking on a temporary name.
345 # implicit zombie filename blocking on a temporary name.
346
346
347 for tries in xrange(10):
347 for tries in xrange(10):
348 temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
348 temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
349 try:
349 try:
350 os.rename(f, temp) # raises OSError EEXIST if temp exists
350 os.rename(f, temp) # raises OSError EEXIST if temp exists
351 break
351 break
352 except OSError, e:
352 except OSError, e:
353 if e.errno != errno.EEXIST:
353 if e.errno != errno.EEXIST:
354 raise
354 raise
355 else:
355 else:
356 raise IOError, (errno.EEXIST, "No usable temporary filename found")
356 raise IOError, (errno.EEXIST, "No usable temporary filename found")
357
357
358 try:
358 try:
359 os.unlink(temp)
359 os.unlink(temp)
360 except OSError:
360 except OSError:
361 # The unlink might have failed because the READONLY attribute may heave
361 # The unlink might have failed because the READONLY attribute may heave
362 # been set on the original file. Rename works fine with READONLY set,
362 # been set on the original file. Rename works fine with READONLY set,
363 # but not os.unlink. Reset all attributes and try again.
363 # but not os.unlink. Reset all attributes and try again.
364 _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
364 _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL)
365 try:
365 try:
366 os.unlink(temp)
366 os.unlink(temp)
367 except OSError:
367 except OSError:
368 # The unlink might have failed due to some very rude AV-Scanners.
368 # The unlink might have failed due to some very rude AV-Scanners.
369 # Leaking a tempfile is the lesser evil than aborting here and
369 # Leaking a tempfile is the lesser evil than aborting here and
370 # leaving some potentially serious inconsistencies.
370 # leaving some potentially serious inconsistencies.
371 pass
371 pass
372
372
373 def makedir(path, notindexed):
373 def makedir(path, notindexed):
374 os.mkdir(path)
374 os.mkdir(path)
375 if notindexed:
375 if notindexed:
376 _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
376 _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
General Comments 0
You need to be logged in to leave comments. Login now