##// END OF EJS Templates
merge: add registry look up bits to tool search
Matt Mackall -
r6006:3c9dbb74 default
parent child Browse files
Show More
@@ -1,194 +1,201 b''
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import util, os, tempfile, context, simplemerge, re
11 11
12 12 def _toolstr(ui, tool, part, default=None):
13 13 return ui.config("merge-tools", tool + "." + part, default)
14 14
15 15 def _toolbool(ui, tool, part, default=False):
16 16 return ui.configbool("merge-tools", tool + "." + part, default)
17 17
18 18 def _findtool(ui, tool):
19 k = _toolstr(ui, tool, "regkey")
20 if k:
21 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
22 if p:
23 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
24 if p:
25 return p
19 26 return util.find_exe(_toolstr(ui, tool, "executable", tool))
20 27
21 28 def _picktool(repo, ui, path, binary, symlink):
22 29 def check(tool, pat, symlink, binary):
23 30 tmsg = tool
24 31 if pat:
25 32 tmsg += " specified for " + pat
26 33 if pat and not _findtool(ui, tool): # skip search if not matching
27 34 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
28 35 elif symlink and not _toolbool(ui, tool, "symlink"):
29 36 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
30 37 elif binary and not _toolbool(ui, tool, "binary"):
31 38 ui.warn(_("tool %s can't handle binary\n") % tmsg)
32 39 else:
33 40 return True
34 41 return False
35 42
36 43 # HGMERGE takes precedence
37 44 if os.environ.get("HGMERGE"):
38 45 return os.environ.get("HGMERGE")
39 46
40 47 # then patterns
41 48 for pattern, tool in ui.configitems("merge-patterns"):
42 49 mf = util.matcher(repo.root, "", [pat], [], [])[1]
43 50 if mf(path) and check(tool, pat, symlink, False):
44 51 return tool
45 52
46 53 # then merge tools
47 54 tools = {}
48 55 for k,v in ui.configitems("merge-tools"):
49 56 t = k.split('.')[0]
50 57 if t not in tools:
51 58 tools[t] = int(_toolstr(ui, t, "priority", "0"))
52 59 tools = [(-p,t) for t,p in tools.items()]
53 60 tools.sort()
54 61 if ui.config("ui", "merge"):
55 62 tools.insert(0, (None, ui.config("ui", "merge"))) # highest priority
56 63 tools.append((None, "hgmerge")) # the old default, if found
57 64 tools.append((None, "internal:merge")) # internal merge as last resort
58 65 for p,t in tools:
59 66 if _findtool(ui, t) and check(t, None, symlink, binary):
60 67 return t
61 68
62 69 def _eoltype(data):
63 70 "Guess the EOL type of a file"
64 71 if '\0' in data: # binary
65 72 return None
66 73 if '\r\n' in data: # Windows
67 74 return '\r\n'
68 75 if '\r' in data: # Old Mac
69 76 return '\r'
70 77 if '\n' in data: # UNIX
71 78 return '\n'
72 79 return None # unknown
73 80
74 81 def _matcheol(file, origfile):
75 82 "Convert EOL markers in a file to match origfile"
76 83 tostyle = _eoltype(open(origfile, "rb").read())
77 84 if tostyle:
78 85 data = open(file, "rb").read()
79 86 style = _eoltype(data)
80 87 if style:
81 88 newdata = data.replace(style, tostyle)
82 89 if newdata != data:
83 90 open(file, "wb").write(newdata)
84 91
85 92 def filemerge(repo, fw, fd, fo, wctx, mctx):
86 93 """perform a 3-way merge in the working directory
87 94
88 95 fw = original filename in the working directory
89 96 fd = destination filename in the working directory
90 97 fo = filename in other parent
91 98 wctx, mctx = working and merge changecontexts
92 99 """
93 100
94 101 def temp(prefix, ctx):
95 102 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
96 103 (fd, name) = tempfile.mkstemp(prefix=pre)
97 104 data = repo.wwritedata(ctx.path(), ctx.data())
98 105 f = os.fdopen(fd, "wb")
99 106 f.write(data)
100 107 f.close()
101 108 return name
102 109
103 110 def isbin(ctx):
104 111 try:
105 112 return util.binary(ctx.data())
106 113 except IOError:
107 114 return False
108 115
109 116 fco = mctx.filectx(fo)
110 117 if not fco.cmp(wctx.filectx(fd).data()): # files identical?
111 118 return None
112 119
113 120 ui = repo.ui
114 121 fcm = wctx.filectx(fw)
115 122 fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev)
116 123 binary = isbin(fcm) or isbin(fco) or isbin(fca)
117 124 symlink = fcm.islink() or fco.islink()
118 125 tool = _picktool(repo, ui, fw, binary, symlink)
119 126 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
120 127 (tool, fw, binary, symlink))
121 128
122 129 if not tool:
123 130 tool = "internal:local"
124 131 if ui.prompt(_(" no tool found to merge %s\n"
125 132 "keep (l)ocal or take (o)ther?") % fw,
126 133 _("[lo]"), _("l")) != _("l"):
127 134 tool = "internal:other"
128 135 if tool == "internal:local":
129 136 return 0
130 137 if tool == "internal:other":
131 138 repo.wwrite(fd, fco.data(), fco.fileflags())
132 139 return 0
133 140 if tool == "internal:fail":
134 141 return 1
135 142
136 143 # do the actual merge
137 144 a = repo.wjoin(fd)
138 145 b = temp("base", fca)
139 146 c = temp("other", fco)
140 147 out = ""
141 148 back = a + ".orig"
142 149 util.copyfile(a, back)
143 150
144 151 if fw != fo:
145 152 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
146 153 else:
147 154 repo.ui.status(_("merging %s\n") % fw)
148 155 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
149 156
150 157 # do we attempt to simplemerge first?
151 158 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
152 159 r = simplemerge.simplemerge(a, b, c, quiet=True)
153 160 if not r:
154 161 ui.debug(_(" premerge successful\n"))
155 162 os.unlink(back)
156 163 os.unlink(b)
157 164 os.unlink(c)
158 165 return 0
159 166 util.copyfile(back, a) # restore from backup and try again
160 167
161 168 env = dict(HG_FILE=fd,
162 169 HG_MY_NODE=str(wctx.parents()[0]),
163 170 HG_OTHER_NODE=str(mctx),
164 171 HG_MY_ISLINK=fcm.islink(),
165 172 HG_OTHER_ISLINK=fco.islink(),
166 173 HG_BASE_ISLINK=fca.islink())
167 174
168 175 if tool == "internal:merge":
169 176 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
170 177 else:
171 178 toolpath = _findtool(ui, tool)
172 179 args = _toolstr(ui, tool, "args", '$local $base $other')
173 180 if "$output" in args:
174 181 out, a = a, back # read input from backup, write to original
175 182 replace = dict(local=a, base=b, other=c, output=out)
176 183 args = re.sub("\$(local|base|other|output)",
177 184 lambda x: '"%s"' % replace[x.group()[1:]], args)
178 185 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
179 186
180 187 if not r and _toolbool(ui, tool, "checkconflicts"):
181 188 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()):
182 189 r = 1
183 190
184 191 if _toolbool(ui, tool, "fixeol"):
185 192 _matcheol(repo.join(fd), back)
186 193
187 194 if r:
188 195 repo.ui.warn(_("merging %s failed!\n") % fd)
189 196 else:
190 197 os.unlink(back)
191 198
192 199 os.unlink(b)
193 200 os.unlink(c)
194 201 return r
@@ -1,1739 +1,1742 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 18 import urlparse
19 19
20 20 try:
21 21 set = set
22 22 frozenset = frozenset
23 23 except NameError:
24 24 from sets import Set as set, ImmutableSet as frozenset
25 25
26 26 try:
27 27 _encoding = os.environ.get("HGENCODING")
28 28 if sys.platform == 'darwin' and not _encoding:
29 29 # On darwin, getpreferredencoding ignores the locale environment and
30 30 # always returns mac-roman. We override this if the environment is
31 31 # not C (has been customized by the user).
32 32 locale.setlocale(locale.LC_CTYPE, '')
33 33 _encoding = locale.getlocale()[1]
34 34 if not _encoding:
35 35 _encoding = locale.getpreferredencoding() or 'ascii'
36 36 except locale.Error:
37 37 _encoding = 'ascii'
38 38 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
39 39 _fallbackencoding = 'ISO-8859-1'
40 40
41 41 def tolocal(s):
42 42 """
43 43 Convert a string from internal UTF-8 to local encoding
44 44
45 45 All internal strings should be UTF-8 but some repos before the
46 46 implementation of locale support may contain latin1 or possibly
47 47 other character sets. We attempt to decode everything strictly
48 48 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
49 49 replace unknown characters.
50 50 """
51 51 for e in ('UTF-8', _fallbackencoding):
52 52 try:
53 53 u = s.decode(e) # attempt strict decoding
54 54 return u.encode(_encoding, "replace")
55 55 except LookupError, k:
56 56 raise Abort(_("%s, please check your locale settings") % k)
57 57 except UnicodeDecodeError:
58 58 pass
59 59 u = s.decode("utf-8", "replace") # last ditch
60 60 return u.encode(_encoding, "replace")
61 61
62 62 def fromlocal(s):
63 63 """
64 64 Convert a string from the local character encoding to UTF-8
65 65
66 66 We attempt to decode strings using the encoding mode set by
67 67 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
68 68 characters will cause an error message. Other modes include
69 69 'replace', which replaces unknown characters with a special
70 70 Unicode character, and 'ignore', which drops the character.
71 71 """
72 72 try:
73 73 return s.decode(_encoding, _encodingmode).encode("utf-8")
74 74 except UnicodeDecodeError, inst:
75 75 sub = s[max(0, inst.start-10):inst.start+10]
76 76 raise Abort("decoding near '%s': %s!" % (sub, inst))
77 77 except LookupError, k:
78 78 raise Abort(_("%s, please check your locale settings") % k)
79 79
80 80 def locallen(s):
81 81 """Find the length in characters of a local string"""
82 82 return len(s.decode(_encoding, "replace"))
83 83
84 84 # used by parsedate
85 85 defaultdateformats = (
86 86 '%Y-%m-%d %H:%M:%S',
87 87 '%Y-%m-%d %I:%M:%S%p',
88 88 '%Y-%m-%d %H:%M',
89 89 '%Y-%m-%d %I:%M%p',
90 90 '%Y-%m-%d',
91 91 '%m-%d',
92 92 '%m/%d',
93 93 '%m/%d/%y',
94 94 '%m/%d/%Y',
95 95 '%a %b %d %H:%M:%S %Y',
96 96 '%a %b %d %I:%M:%S%p %Y',
97 97 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
98 98 '%b %d %H:%M:%S %Y',
99 99 '%b %d %I:%M:%S%p %Y',
100 100 '%b %d %H:%M:%S',
101 101 '%b %d %I:%M:%S%p',
102 102 '%b %d %H:%M',
103 103 '%b %d %I:%M%p',
104 104 '%b %d %Y',
105 105 '%b %d',
106 106 '%H:%M:%S',
107 107 '%I:%M:%SP',
108 108 '%H:%M',
109 109 '%I:%M%p',
110 110 )
111 111
112 112 extendeddateformats = defaultdateformats + (
113 113 "%Y",
114 114 "%Y-%m",
115 115 "%b",
116 116 "%b %Y",
117 117 )
118 118
119 119 class SignalInterrupt(Exception):
120 120 """Exception raised on SIGTERM and SIGHUP."""
121 121
122 122 # differences from SafeConfigParser:
123 123 # - case-sensitive keys
124 124 # - allows values that are not strings (this means that you may not
125 125 # be able to save the configuration to a file)
126 126 class configparser(ConfigParser.SafeConfigParser):
127 127 def optionxform(self, optionstr):
128 128 return optionstr
129 129
130 130 def set(self, section, option, value):
131 131 return ConfigParser.ConfigParser.set(self, section, option, value)
132 132
133 133 def _interpolate(self, section, option, rawval, vars):
134 134 if not isinstance(rawval, basestring):
135 135 return rawval
136 136 return ConfigParser.SafeConfigParser._interpolate(self, section,
137 137 option, rawval, vars)
138 138
139 139 def cachefunc(func):
140 140 '''cache the result of function calls'''
141 141 # XXX doesn't handle keywords args
142 142 cache = {}
143 143 if func.func_code.co_argcount == 1:
144 144 # we gain a small amount of time because
145 145 # we don't need to pack/unpack the list
146 146 def f(arg):
147 147 if arg not in cache:
148 148 cache[arg] = func(arg)
149 149 return cache[arg]
150 150 else:
151 151 def f(*args):
152 152 if args not in cache:
153 153 cache[args] = func(*args)
154 154 return cache[args]
155 155
156 156 return f
157 157
158 158 def pipefilter(s, cmd):
159 159 '''filter string S through command CMD, returning its output'''
160 160 (pin, pout) = os.popen2(cmd, 'b')
161 161 def writer():
162 162 try:
163 163 pin.write(s)
164 164 pin.close()
165 165 except IOError, inst:
166 166 if inst.errno != errno.EPIPE:
167 167 raise
168 168
169 169 # we should use select instead on UNIX, but this will work on most
170 170 # systems, including Windows
171 171 w = threading.Thread(target=writer)
172 172 w.start()
173 173 f = pout.read()
174 174 pout.close()
175 175 w.join()
176 176 return f
177 177
178 178 def tempfilter(s, cmd):
179 179 '''filter string S through a pair of temporary files with CMD.
180 180 CMD is used as a template to create the real command to be run,
181 181 with the strings INFILE and OUTFILE replaced by the real names of
182 182 the temporary files generated.'''
183 183 inname, outname = None, None
184 184 try:
185 185 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
186 186 fp = os.fdopen(infd, 'wb')
187 187 fp.write(s)
188 188 fp.close()
189 189 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
190 190 os.close(outfd)
191 191 cmd = cmd.replace('INFILE', inname)
192 192 cmd = cmd.replace('OUTFILE', outname)
193 193 code = os.system(cmd)
194 194 if sys.platform == 'OpenVMS' and code & 1:
195 195 code = 0
196 196 if code: raise Abort(_("command '%s' failed: %s") %
197 197 (cmd, explain_exit(code)))
198 198 return open(outname, 'rb').read()
199 199 finally:
200 200 try:
201 201 if inname: os.unlink(inname)
202 202 except: pass
203 203 try:
204 204 if outname: os.unlink(outname)
205 205 except: pass
206 206
207 207 filtertable = {
208 208 'tempfile:': tempfilter,
209 209 'pipe:': pipefilter,
210 210 }
211 211
212 212 def filter(s, cmd):
213 213 "filter a string through a command that transforms its input to its output"
214 214 for name, fn in filtertable.iteritems():
215 215 if cmd.startswith(name):
216 216 return fn(s, cmd[len(name):].lstrip())
217 217 return pipefilter(s, cmd)
218 218
219 219 def binary(s):
220 220 """return true if a string is binary data using diff's heuristic"""
221 221 if s and '\0' in s[:4096]:
222 222 return True
223 223 return False
224 224
225 225 def unique(g):
226 226 """return the uniq elements of iterable g"""
227 227 return dict.fromkeys(g).keys()
228 228
229 229 class Abort(Exception):
230 230 """Raised if a command needs to print an error and exit."""
231 231
232 232 class UnexpectedOutput(Abort):
233 233 """Raised to print an error with part of output and exit."""
234 234
235 235 def always(fn): return True
236 236 def never(fn): return False
237 237
238 238 def expand_glob(pats):
239 239 '''On Windows, expand the implicit globs in a list of patterns'''
240 240 if os.name != 'nt':
241 241 return list(pats)
242 242 ret = []
243 243 for p in pats:
244 244 kind, name = patkind(p, None)
245 245 if kind is None:
246 246 globbed = glob.glob(name)
247 247 if globbed:
248 248 ret.extend(globbed)
249 249 continue
250 250 # if we couldn't expand the glob, just keep it around
251 251 ret.append(p)
252 252 return ret
253 253
254 254 def patkind(name, dflt_pat='glob'):
255 255 """Split a string into an optional pattern kind prefix and the
256 256 actual pattern."""
257 257 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
258 258 if name.startswith(prefix + ':'): return name.split(':', 1)
259 259 return dflt_pat, name
260 260
261 261 def globre(pat, head='^', tail='$'):
262 262 "convert a glob pattern into a regexp"
263 263 i, n = 0, len(pat)
264 264 res = ''
265 265 group = 0
266 266 def peek(): return i < n and pat[i]
267 267 while i < n:
268 268 c = pat[i]
269 269 i = i+1
270 270 if c == '*':
271 271 if peek() == '*':
272 272 i += 1
273 273 res += '.*'
274 274 else:
275 275 res += '[^/]*'
276 276 elif c == '?':
277 277 res += '.'
278 278 elif c == '[':
279 279 j = i
280 280 if j < n and pat[j] in '!]':
281 281 j += 1
282 282 while j < n and pat[j] != ']':
283 283 j += 1
284 284 if j >= n:
285 285 res += '\\['
286 286 else:
287 287 stuff = pat[i:j].replace('\\','\\\\')
288 288 i = j + 1
289 289 if stuff[0] == '!':
290 290 stuff = '^' + stuff[1:]
291 291 elif stuff[0] == '^':
292 292 stuff = '\\' + stuff
293 293 res = '%s[%s]' % (res, stuff)
294 294 elif c == '{':
295 295 group += 1
296 296 res += '(?:'
297 297 elif c == '}' and group:
298 298 res += ')'
299 299 group -= 1
300 300 elif c == ',' and group:
301 301 res += '|'
302 302 elif c == '\\':
303 303 p = peek()
304 304 if p:
305 305 i += 1
306 306 res += re.escape(p)
307 307 else:
308 308 res += re.escape(c)
309 309 else:
310 310 res += re.escape(c)
311 311 return head + res + tail
312 312
313 313 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
314 314
315 315 def pathto(root, n1, n2):
316 316 '''return the relative path from one place to another.
317 317 root should use os.sep to separate directories
318 318 n1 should use os.sep to separate directories
319 319 n2 should use "/" to separate directories
320 320 returns an os.sep-separated path.
321 321
322 322 If n1 is a relative path, it's assumed it's
323 323 relative to root.
324 324 n2 should always be relative to root.
325 325 '''
326 326 if not n1: return localpath(n2)
327 327 if os.path.isabs(n1):
328 328 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
329 329 return os.path.join(root, localpath(n2))
330 330 n2 = '/'.join((pconvert(root), n2))
331 331 a, b = splitpath(n1), n2.split('/')
332 332 a.reverse()
333 333 b.reverse()
334 334 while a and b and a[-1] == b[-1]:
335 335 a.pop()
336 336 b.pop()
337 337 b.reverse()
338 338 return os.sep.join((['..'] * len(a)) + b)
339 339
340 340 def canonpath(root, cwd, myname):
341 341 """return the canonical path of myname, given cwd and root"""
342 342 if root == os.sep:
343 343 rootsep = os.sep
344 344 elif endswithsep(root):
345 345 rootsep = root
346 346 else:
347 347 rootsep = root + os.sep
348 348 name = myname
349 349 if not os.path.isabs(name):
350 350 name = os.path.join(root, cwd, name)
351 351 name = os.path.normpath(name)
352 352 audit_path = path_auditor(root)
353 353 if name != rootsep and name.startswith(rootsep):
354 354 name = name[len(rootsep):]
355 355 audit_path(name)
356 356 return pconvert(name)
357 357 elif name == root:
358 358 return ''
359 359 else:
360 360 # Determine whether `name' is in the hierarchy at or beneath `root',
361 361 # by iterating name=dirname(name) until that causes no change (can't
362 362 # check name == '/', because that doesn't work on windows). For each
363 363 # `name', compare dev/inode numbers. If they match, the list `rel'
364 364 # holds the reversed list of components making up the relative file
365 365 # name we want.
366 366 root_st = os.stat(root)
367 367 rel = []
368 368 while True:
369 369 try:
370 370 name_st = os.stat(name)
371 371 except OSError:
372 372 break
373 373 if samestat(name_st, root_st):
374 374 if not rel:
375 375 # name was actually the same as root (maybe a symlink)
376 376 return ''
377 377 rel.reverse()
378 378 name = os.path.join(*rel)
379 379 audit_path(name)
380 380 return pconvert(name)
381 381 dirname, basename = os.path.split(name)
382 382 rel.append(basename)
383 383 if dirname == name:
384 384 break
385 385 name = dirname
386 386
387 387 raise Abort('%s not under root' % myname)
388 388
389 389 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
390 390 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
391 391
392 392 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
393 393 globbed=False, default=None):
394 394 default = default or 'relpath'
395 395 if default == 'relpath' and not globbed:
396 396 names = expand_glob(names)
397 397 return _matcher(canonroot, cwd, names, inc, exc, default, src)
398 398
399 399 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
400 400 """build a function to match a set of file patterns
401 401
402 402 arguments:
403 403 canonroot - the canonical root of the tree you're matching against
404 404 cwd - the current working directory, if relevant
405 405 names - patterns to find
406 406 inc - patterns to include
407 407 exc - patterns to exclude
408 408 dflt_pat - if a pattern in names has no explicit type, assume this one
409 409 src - where these patterns came from (e.g. .hgignore)
410 410
411 411 a pattern is one of:
412 412 'glob:<glob>' - a glob relative to cwd
413 413 're:<regexp>' - a regular expression
414 414 'path:<path>' - a path relative to canonroot
415 415 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
416 416 'relpath:<path>' - a path relative to cwd
417 417 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
418 418 '<something>' - one of the cases above, selected by the dflt_pat argument
419 419
420 420 returns:
421 421 a 3-tuple containing
422 422 - list of roots (places where one should start a recursive walk of the fs);
423 423 this often matches the explicit non-pattern names passed in, but also
424 424 includes the initial part of glob: patterns that has no glob characters
425 425 - a bool match(filename) function
426 426 - a bool indicating if any patterns were passed in
427 427 """
428 428
429 429 # a common case: no patterns at all
430 430 if not names and not inc and not exc:
431 431 return [], always, False
432 432
433 433 def contains_glob(name):
434 434 for c in name:
435 435 if c in _globchars: return True
436 436 return False
437 437
438 438 def regex(kind, name, tail):
439 439 '''convert a pattern into a regular expression'''
440 440 if not name:
441 441 return ''
442 442 if kind == 're':
443 443 return name
444 444 elif kind == 'path':
445 445 return '^' + re.escape(name) + '(?:/|$)'
446 446 elif kind == 'relglob':
447 447 return globre(name, '(?:|.*/)', tail)
448 448 elif kind == 'relpath':
449 449 return re.escape(name) + '(?:/|$)'
450 450 elif kind == 'relre':
451 451 if name.startswith('^'):
452 452 return name
453 453 return '.*' + name
454 454 return globre(name, '', tail)
455 455
456 456 def matchfn(pats, tail):
457 457 """build a matching function from a set of patterns"""
458 458 if not pats:
459 459 return
460 460 try:
461 461 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
462 462 return re.compile(pat).match
463 463 except OverflowError:
464 464 # We're using a Python with a tiny regex engine and we
465 465 # made it explode, so we'll divide the pattern list in two
466 466 # until it works
467 467 l = len(pats)
468 468 if l < 2:
469 469 raise
470 470 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
471 471 return lambda s: a(s) or b(s)
472 472 except re.error:
473 473 for k, p in pats:
474 474 try:
475 475 re.compile('(?:%s)' % regex(k, p, tail))
476 476 except re.error:
477 477 if src:
478 478 raise Abort("%s: invalid pattern (%s): %s" %
479 479 (src, k, p))
480 480 else:
481 481 raise Abort("invalid pattern (%s): %s" % (k, p))
482 482 raise Abort("invalid pattern")
483 483
484 484 def globprefix(pat):
485 485 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
486 486 root = []
487 487 for p in pat.split('/'):
488 488 if contains_glob(p): break
489 489 root.append(p)
490 490 return '/'.join(root) or '.'
491 491
492 492 def normalizepats(names, default):
493 493 pats = []
494 494 roots = []
495 495 anypats = False
496 496 for kind, name in [patkind(p, default) for p in names]:
497 497 if kind in ('glob', 'relpath'):
498 498 name = canonpath(canonroot, cwd, name)
499 499 elif kind in ('relglob', 'path'):
500 500 name = normpath(name)
501 501
502 502 pats.append((kind, name))
503 503
504 504 if kind in ('glob', 're', 'relglob', 'relre'):
505 505 anypats = True
506 506
507 507 if kind == 'glob':
508 508 root = globprefix(name)
509 509 roots.append(root)
510 510 elif kind in ('relpath', 'path'):
511 511 roots.append(name or '.')
512 512 elif kind == 'relglob':
513 513 roots.append('.')
514 514 return roots, pats, anypats
515 515
516 516 roots, pats, anypats = normalizepats(names, dflt_pat)
517 517
518 518 patmatch = matchfn(pats, '$') or always
519 519 incmatch = always
520 520 if inc:
521 521 dummy, inckinds, dummy = normalizepats(inc, 'glob')
522 522 incmatch = matchfn(inckinds, '(?:/|$)')
523 523 excmatch = lambda fn: False
524 524 if exc:
525 525 dummy, exckinds, dummy = normalizepats(exc, 'glob')
526 526 excmatch = matchfn(exckinds, '(?:/|$)')
527 527
528 528 if not names and inc and not exc:
529 529 # common case: hgignore patterns
530 530 match = incmatch
531 531 else:
532 532 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
533 533
534 534 return (roots, match, (inc or exc or anypats) and True)
535 535
536 536 _hgexecutable = None
537 537
538 538 def hgexecutable():
539 539 """return location of the 'hg' executable.
540 540
541 541 Defaults to $HG or 'hg' in the search path.
542 542 """
543 543 if _hgexecutable is None:
544 544 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
545 545 return _hgexecutable
546 546
547 547 def set_hgexecutable(path):
548 548 """set location of the 'hg' executable"""
549 549 global _hgexecutable
550 550 _hgexecutable = path
551 551
552 552 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
553 553 '''enhanced shell command execution.
554 554 run with environment maybe modified, maybe in different dir.
555 555
556 556 if command fails and onerr is None, return status. if ui object,
557 557 print error message and return status, else raise onerr object as
558 558 exception.'''
559 559 def py2shell(val):
560 560 'convert python object into string that is useful to shell'
561 561 if val in (None, False):
562 562 return '0'
563 563 if val == True:
564 564 return '1'
565 565 return str(val)
566 566 oldenv = {}
567 567 for k in environ:
568 568 oldenv[k] = os.environ.get(k)
569 569 if cwd is not None:
570 570 oldcwd = os.getcwd()
571 571 origcmd = cmd
572 572 if os.name == 'nt':
573 573 cmd = '"%s"' % cmd
574 574 try:
575 575 for k, v in environ.iteritems():
576 576 os.environ[k] = py2shell(v)
577 577 os.environ['HG'] = hgexecutable()
578 578 if cwd is not None and oldcwd != cwd:
579 579 os.chdir(cwd)
580 580 rc = os.system(cmd)
581 581 if sys.platform == 'OpenVMS' and rc & 1:
582 582 rc = 0
583 583 if rc and onerr:
584 584 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
585 585 explain_exit(rc)[0])
586 586 if errprefix:
587 587 errmsg = '%s: %s' % (errprefix, errmsg)
588 588 try:
589 589 onerr.warn(errmsg + '\n')
590 590 except AttributeError:
591 591 raise onerr(errmsg)
592 592 return rc
593 593 finally:
594 594 for k, v in oldenv.iteritems():
595 595 if v is None:
596 596 del os.environ[k]
597 597 else:
598 598 os.environ[k] = v
599 599 if cwd is not None and oldcwd != cwd:
600 600 os.chdir(oldcwd)
601 601
602 602 # os.path.lexists is not available on python2.3
603 603 def lexists(filename):
604 604 "test whether a file with this name exists. does not follow symlinks"
605 605 try:
606 606 os.lstat(filename)
607 607 except:
608 608 return False
609 609 return True
610 610
611 611 def rename(src, dst):
612 612 """forcibly rename a file"""
613 613 try:
614 614 os.rename(src, dst)
615 615 except OSError, err: # FIXME: check err (EEXIST ?)
616 616 # on windows, rename to existing file is not allowed, so we
617 617 # must delete destination first. but if file is open, unlink
618 618 # schedules it for delete but does not delete it. rename
619 619 # happens immediately even for open files, so we create
620 620 # temporary file, delete it, rename destination to that name,
621 621 # then delete that. then rename is safe to do.
622 622 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
623 623 os.close(fd)
624 624 os.unlink(temp)
625 625 os.rename(dst, temp)
626 626 os.unlink(temp)
627 627 os.rename(src, dst)
628 628
629 629 def unlink(f):
630 630 """unlink and remove the directory if it is empty"""
631 631 os.unlink(f)
632 632 # try removing directories that might now be empty
633 633 try:
634 634 os.removedirs(os.path.dirname(f))
635 635 except OSError:
636 636 pass
637 637
638 638 def copyfile(src, dest):
639 639 "copy a file, preserving mode"
640 640 if os.path.islink(src):
641 641 try:
642 642 os.unlink(dest)
643 643 except:
644 644 pass
645 645 os.symlink(os.readlink(src), dest)
646 646 else:
647 647 try:
648 648 shutil.copyfile(src, dest)
649 649 shutil.copymode(src, dest)
650 650 except shutil.Error, inst:
651 651 raise Abort(str(inst))
652 652
653 653 def copyfiles(src, dst, hardlink=None):
654 654 """Copy a directory tree using hardlinks if possible"""
655 655
656 656 if hardlink is None:
657 657 hardlink = (os.stat(src).st_dev ==
658 658 os.stat(os.path.dirname(dst)).st_dev)
659 659
660 660 if os.path.isdir(src):
661 661 os.mkdir(dst)
662 662 for name, kind in osutil.listdir(src):
663 663 srcname = os.path.join(src, name)
664 664 dstname = os.path.join(dst, name)
665 665 copyfiles(srcname, dstname, hardlink)
666 666 else:
667 667 if hardlink:
668 668 try:
669 669 os_link(src, dst)
670 670 except (IOError, OSError):
671 671 hardlink = False
672 672 shutil.copy(src, dst)
673 673 else:
674 674 shutil.copy(src, dst)
675 675
676 676 class path_auditor(object):
677 677 '''ensure that a filesystem path contains no banned components.
678 678 the following properties of a path are checked:
679 679
680 680 - under top-level .hg
681 681 - starts at the root of a windows drive
682 682 - contains ".."
683 683 - traverses a symlink (e.g. a/symlink_here/b)
684 684 - inside a nested repository'''
685 685
686 686 def __init__(self, root):
687 687 self.audited = set()
688 688 self.auditeddir = set()
689 689 self.root = root
690 690
691 691 def __call__(self, path):
692 692 if path in self.audited:
693 693 return
694 694 normpath = os.path.normcase(path)
695 695 parts = splitpath(normpath)
696 696 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
697 697 or os.pardir in parts):
698 698 raise Abort(_("path contains illegal component: %s") % path)
699 699 def check(prefix):
700 700 curpath = os.path.join(self.root, prefix)
701 701 try:
702 702 st = os.lstat(curpath)
703 703 except OSError, err:
704 704 # EINVAL can be raised as invalid path syntax under win32.
705 705 # They must be ignored for patterns can be checked too.
706 706 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
707 707 raise
708 708 else:
709 709 if stat.S_ISLNK(st.st_mode):
710 710 raise Abort(_('path %r traverses symbolic link %r') %
711 711 (path, prefix))
712 712 elif (stat.S_ISDIR(st.st_mode) and
713 713 os.path.isdir(os.path.join(curpath, '.hg'))):
714 714 raise Abort(_('path %r is inside repo %r') %
715 715 (path, prefix))
716 716 parts.pop()
717 717 prefixes = []
718 718 for n in range(len(parts)):
719 719 prefix = os.sep.join(parts)
720 720 if prefix in self.auditeddir:
721 721 break
722 722 check(prefix)
723 723 prefixes.append(prefix)
724 724 parts.pop()
725 725
726 726 self.audited.add(path)
727 727 # only add prefixes to the cache after checking everything: we don't
728 728 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
729 729 self.auditeddir.update(prefixes)
730 730
731 731 def _makelock_file(info, pathname):
732 732 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
733 733 os.write(ld, info)
734 734 os.close(ld)
735 735
736 736 def _readlock_file(pathname):
737 737 return posixfile(pathname).read()
738 738
739 739 def nlinks(pathname):
740 740 """Return number of hardlinks for the given file."""
741 741 return os.lstat(pathname).st_nlink
742 742
743 743 if hasattr(os, 'link'):
744 744 os_link = os.link
745 745 else:
746 746 def os_link(src, dst):
747 747 raise OSError(0, _("Hardlinks not supported"))
748 748
749 749 def fstat(fp):
750 750 '''stat file object that may not have fileno method.'''
751 751 try:
752 752 return os.fstat(fp.fileno())
753 753 except AttributeError:
754 754 return os.stat(fp.name)
755 755
756 756 posixfile = file
757 757
758 758 def openhardlinks():
759 759 '''return true if it is safe to hold open file handles to hardlinks'''
760 760 return True
761 761
762 762 getuser_fallback = None
763 763
764 764 def getuser():
765 765 '''return name of current user'''
766 766 try:
767 767 return getpass.getuser()
768 768 except ImportError:
769 769 # import of pwd will fail on windows - try fallback
770 770 if getuser_fallback:
771 771 return getuser_fallback()
772 772 # raised if win32api not available
773 773 raise Abort(_('user name not available - set USERNAME '
774 774 'environment variable'))
775 775
776 776 def username(uid=None):
777 777 """Return the name of the user with the given uid.
778 778
779 779 If uid is None, return the name of the current user."""
780 780 try:
781 781 import pwd
782 782 if uid is None:
783 783 uid = os.getuid()
784 784 try:
785 785 return pwd.getpwuid(uid)[0]
786 786 except KeyError:
787 787 return str(uid)
788 788 except ImportError:
789 789 return None
790 790
791 791 def groupname(gid=None):
792 792 """Return the name of the group with the given gid.
793 793
794 794 If gid is None, return the name of the current group."""
795 795 try:
796 796 import grp
797 797 if gid is None:
798 798 gid = os.getgid()
799 799 try:
800 800 return grp.getgrgid(gid)[0]
801 801 except KeyError:
802 802 return str(gid)
803 803 except ImportError:
804 804 return None
805 805
806 806 # File system features
807 807
808 808 def checkfolding(path):
809 809 """
810 810 Check whether the given path is on a case-sensitive filesystem
811 811
812 812 Requires a path (like /foo/.hg) ending with a foldable final
813 813 directory component.
814 814 """
815 815 s1 = os.stat(path)
816 816 d, b = os.path.split(path)
817 817 p2 = os.path.join(d, b.upper())
818 818 if path == p2:
819 819 p2 = os.path.join(d, b.lower())
820 820 try:
821 821 s2 = os.stat(p2)
822 822 if s2 == s1:
823 823 return False
824 824 return True
825 825 except:
826 826 return True
827 827
828 828 def checkexec(path):
829 829 """
830 830 Check whether the given path is on a filesystem with UNIX-like exec flags
831 831
832 832 Requires a directory (like /foo/.hg)
833 833 """
834 834
835 835 # VFAT on some Linux versions can flip mode but it doesn't persist
836 836 # a FS remount. Frequently we can detect it if files are created
837 837 # with exec bit on.
838 838
839 839 try:
840 840 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
841 841 fh, fn = tempfile.mkstemp("", "", path)
842 842 try:
843 843 os.close(fh)
844 844 m = os.stat(fn).st_mode & 0777
845 845 new_file_has_exec = m & EXECFLAGS
846 846 os.chmod(fn, m ^ EXECFLAGS)
847 847 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
848 848 finally:
849 849 os.unlink(fn)
850 850 except (IOError, OSError):
851 851 # we don't care, the user probably won't be able to commit anyway
852 852 return False
853 853 return not (new_file_has_exec or exec_flags_cannot_flip)
854 854
855 855 def execfunc(path, fallback):
856 856 '''return an is_exec() function with default to fallback'''
857 857 if checkexec(path):
858 858 return lambda x: is_exec(os.path.join(path, x))
859 859 return fallback
860 860
861 861 def checklink(path):
862 862 """check whether the given path is on a symlink-capable filesystem"""
863 863 # mktemp is not racy because symlink creation will fail if the
864 864 # file already exists
865 865 name = tempfile.mktemp(dir=path)
866 866 try:
867 867 os.symlink(".", name)
868 868 os.unlink(name)
869 869 return True
870 870 except (OSError, AttributeError):
871 871 return False
872 872
873 873 def linkfunc(path, fallback):
874 874 '''return an is_link() function with default to fallback'''
875 875 if checklink(path):
876 876 return lambda x: os.path.islink(os.path.join(path, x))
877 877 return fallback
878 878
879 879 _umask = os.umask(0)
880 880 os.umask(_umask)
881 881
882 882 def needbinarypatch():
883 883 """return True if patches should be applied in binary mode by default."""
884 884 return os.name == 'nt'
885 885
886 886 def endswithsep(path):
887 887 '''Check path ends with os.sep or os.altsep.'''
888 888 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
889 889
890 890 def splitpath(path):
891 891 '''Split path by os.sep.
892 892 Note that this function does not use os.altsep because this is
893 893 an alternative of simple "xxx.split(os.sep)".
894 894 It is recommended to use os.path.normpath() before using this
895 895 function if need.'''
896 896 return path.split(os.sep)
897 897
898 898 # Platform specific variants
899 899 if os.name == 'nt':
900 900 import msvcrt
901 901 nulldev = 'NUL:'
902 902
903 903 class winstdout:
904 904 '''stdout on windows misbehaves if sent through a pipe'''
905 905
906 906 def __init__(self, fp):
907 907 self.fp = fp
908 908
909 909 def __getattr__(self, key):
910 910 return getattr(self.fp, key)
911 911
912 912 def close(self):
913 913 try:
914 914 self.fp.close()
915 915 except: pass
916 916
917 917 def write(self, s):
918 918 try:
919 919 # This is workaround for "Not enough space" error on
920 920 # writing large size of data to console.
921 921 limit = 16000
922 922 l = len(s)
923 923 start = 0
924 924 while start < l:
925 925 end = start + limit
926 926 self.fp.write(s[start:end])
927 927 start = end
928 928 except IOError, inst:
929 929 if inst.errno != 0: raise
930 930 self.close()
931 931 raise IOError(errno.EPIPE, 'Broken pipe')
932 932
933 933 def flush(self):
934 934 try:
935 935 return self.fp.flush()
936 936 except IOError, inst:
937 937 if inst.errno != errno.EINVAL: raise
938 938 self.close()
939 939 raise IOError(errno.EPIPE, 'Broken pipe')
940 940
941 941 sys.stdout = winstdout(sys.stdout)
942 942
943 943 def _is_win_9x():
944 944 '''return true if run on windows 95, 98 or me.'''
945 945 try:
946 946 return sys.getwindowsversion()[3] == 1
947 947 except AttributeError:
948 948 return 'command' in os.environ.get('comspec', '')
949 949
950 950 def openhardlinks():
951 951 return not _is_win_9x and "win32api" in locals()
952 952
953 953 def system_rcpath():
954 954 try:
955 955 return system_rcpath_win32()
956 956 except:
957 957 return [r'c:\mercurial\mercurial.ini']
958 958
959 959 def user_rcpath():
960 960 '''return os-specific hgrc search path to the user dir'''
961 961 try:
962 962 userrc = user_rcpath_win32()
963 963 except:
964 964 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
965 965 path = [userrc]
966 966 userprofile = os.environ.get('USERPROFILE')
967 967 if userprofile:
968 968 path.append(os.path.join(userprofile, 'mercurial.ini'))
969 969 return path
970 970
971 971 def parse_patch_output(output_line):
972 972 """parses the output produced by patch and returns the file name"""
973 973 pf = output_line[14:]
974 974 if pf[0] == '`':
975 975 pf = pf[1:-1] # Remove the quotes
976 976 return pf
977 977
978 978 def sshargs(sshcmd, host, user, port):
979 979 '''Build argument list for ssh or Plink'''
980 980 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
981 981 args = user and ("%s@%s" % (user, host)) or host
982 982 return port and ("%s %s %s" % (args, pflag, port)) or args
983 983
984 984 def testpid(pid):
985 985 '''return False if pid dead, True if running or not known'''
986 986 return True
987 987
988 988 def set_flags(f, flags):
989 989 pass
990 990
991 991 def set_binary(fd):
992 992 msvcrt.setmode(fd.fileno(), os.O_BINARY)
993 993
994 994 def pconvert(path):
995 995 return '/'.join(splitpath(path))
996 996
997 997 def localpath(path):
998 998 return path.replace('/', '\\')
999 999
1000 1000 def normpath(path):
1001 1001 return pconvert(os.path.normpath(path))
1002 1002
1003 1003 makelock = _makelock_file
1004 1004 readlock = _readlock_file
1005 1005
1006 1006 def samestat(s1, s2):
1007 1007 return False
1008 1008
1009 1009 # A sequence of backslashes is special iff it precedes a double quote:
1010 1010 # - if there's an even number of backslashes, the double quote is not
1011 1011 # quoted (i.e. it ends the quoted region)
1012 1012 # - if there's an odd number of backslashes, the double quote is quoted
1013 1013 # - in both cases, every pair of backslashes is unquoted into a single
1014 1014 # backslash
1015 1015 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1016 1016 # So, to quote a string, we must surround it in double quotes, double
1017 1017 # the number of backslashes that preceed double quotes and add another
1018 1018 # backslash before every double quote (being careful with the double
1019 1019 # quote we've appended to the end)
1020 1020 _quotere = None
1021 1021 def shellquote(s):
1022 1022 global _quotere
1023 1023 if _quotere is None:
1024 1024 _quotere = re.compile(r'(\\*)("|\\$)')
1025 1025 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1026 1026
1027 1027 def quotecommand(cmd):
1028 1028 """Build a command string suitable for os.popen* calls."""
1029 1029 # The extra quotes are needed because popen* runs the command
1030 1030 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1031 1031 return '"' + cmd + '"'
1032 1032
1033 1033 def popen(command):
1034 1034 # Work around "popen spawned process may not write to stdout
1035 1035 # under windows"
1036 1036 # http://bugs.python.org/issue1366
1037 1037 command += " 2> %s" % nulldev
1038 1038 return os.popen(quotecommand(command))
1039 1039
1040 1040 def explain_exit(code):
1041 1041 return _("exited with status %d") % code, code
1042 1042
1043 1043 # if you change this stub into a real check, please try to implement the
1044 1044 # username and groupname functions above, too.
1045 1045 def isowner(fp, st=None):
1046 1046 return True
1047 1047
1048 1048 def find_in_path(name, path, default=None):
1049 1049 '''find name in search path. path can be string (will be split
1050 1050 with os.pathsep), or iterable thing that returns strings. if name
1051 1051 found, return path to name. else return default. name is looked up
1052 1052 using cmd.exe rules, using PATHEXT.'''
1053 1053 if isinstance(path, str):
1054 1054 path = path.split(os.pathsep)
1055 1055
1056 1056 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1057 1057 pathext = pathext.lower().split(os.pathsep)
1058 1058 isexec = os.path.splitext(name)[1].lower() in pathext
1059 1059
1060 1060 for p in path:
1061 1061 p_name = os.path.join(p, name)
1062 1062
1063 1063 if isexec and os.path.exists(p_name):
1064 1064 return p_name
1065 1065
1066 1066 for ext in pathext:
1067 1067 p_name_ext = p_name + ext
1068 1068 if os.path.exists(p_name_ext):
1069 1069 return p_name_ext
1070 1070 return default
1071 1071
1072 1072 def set_signal_handler():
1073 1073 try:
1074 1074 set_signal_handler_win32()
1075 1075 except NameError:
1076 1076 pass
1077 1077
1078 1078 try:
1079 1079 # override functions with win32 versions if possible
1080 1080 from util_win32 import *
1081 1081 if not _is_win_9x():
1082 1082 posixfile = posixfile_nt
1083 1083 except ImportError:
1084 1084 pass
1085 1085
1086 1086 else:
1087 1087 nulldev = '/dev/null'
1088 1088
1089 def lookup_reg(key, name=None, scope=None):
1090 return None
1091
1089 1092 def rcfiles(path):
1090 1093 rcs = [os.path.join(path, 'hgrc')]
1091 1094 rcdir = os.path.join(path, 'hgrc.d')
1092 1095 try:
1093 1096 rcs.extend([os.path.join(rcdir, f)
1094 1097 for f, kind in osutil.listdir(rcdir)
1095 1098 if f.endswith(".rc")])
1096 1099 except OSError:
1097 1100 pass
1098 1101 return rcs
1099 1102
1100 1103 def system_rcpath():
1101 1104 path = []
1102 1105 # old mod_python does not set sys.argv
1103 1106 if len(getattr(sys, 'argv', [])) > 0:
1104 1107 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1105 1108 '/../etc/mercurial'))
1106 1109 path.extend(rcfiles('/etc/mercurial'))
1107 1110 return path
1108 1111
1109 1112 def user_rcpath():
1110 1113 return [os.path.expanduser('~/.hgrc')]
1111 1114
1112 1115 def parse_patch_output(output_line):
1113 1116 """parses the output produced by patch and returns the file name"""
1114 1117 pf = output_line[14:]
1115 1118 if os.sys.platform == 'OpenVMS':
1116 1119 if pf[0] == '`':
1117 1120 pf = pf[1:-1] # Remove the quotes
1118 1121 else:
1119 1122 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1120 1123 pf = pf[1:-1] # Remove the quotes
1121 1124 return pf
1122 1125
1123 1126 def sshargs(sshcmd, host, user, port):
1124 1127 '''Build argument list for ssh'''
1125 1128 args = user and ("%s@%s" % (user, host)) or host
1126 1129 return port and ("%s -p %s" % (args, port)) or args
1127 1130
1128 1131 def is_exec(f):
1129 1132 """check whether a file is executable"""
1130 1133 return (os.lstat(f).st_mode & 0100 != 0)
1131 1134
1132 1135 def set_flags(f, flags):
1133 1136 s = os.lstat(f).st_mode
1134 1137 x = "x" in flags
1135 1138 l = "l" in flags
1136 1139 if l:
1137 1140 if not stat.S_ISLNK(s):
1138 1141 # switch file to link
1139 1142 data = file(f).read()
1140 1143 os.unlink(f)
1141 1144 os.symlink(data, f)
1142 1145 # no chmod needed at this point
1143 1146 return
1144 1147 if stat.S_ISLNK(s):
1145 1148 # switch link to file
1146 1149 data = os.readlink(f)
1147 1150 os.unlink(f)
1148 1151 file(f, "w").write(data)
1149 1152 s = 0666 & ~_umask # avoid restatting for chmod
1150 1153
1151 1154 sx = s & 0100
1152 1155 if x and not sx:
1153 1156 # Turn on +x for every +r bit when making a file executable
1154 1157 # and obey umask.
1155 1158 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1156 1159 elif not x and sx:
1157 1160 # Turn off all +x bits
1158 1161 os.chmod(f, s & 0666)
1159 1162
1160 1163 def set_binary(fd):
1161 1164 pass
1162 1165
1163 1166 def pconvert(path):
1164 1167 return path
1165 1168
1166 1169 def localpath(path):
1167 1170 return path
1168 1171
1169 1172 normpath = os.path.normpath
1170 1173 samestat = os.path.samestat
1171 1174
1172 1175 def makelock(info, pathname):
1173 1176 try:
1174 1177 os.symlink(info, pathname)
1175 1178 except OSError, why:
1176 1179 if why.errno == errno.EEXIST:
1177 1180 raise
1178 1181 else:
1179 1182 _makelock_file(info, pathname)
1180 1183
1181 1184 def readlock(pathname):
1182 1185 try:
1183 1186 return os.readlink(pathname)
1184 1187 except OSError, why:
1185 1188 if why.errno in (errno.EINVAL, errno.ENOSYS):
1186 1189 return _readlock_file(pathname)
1187 1190 else:
1188 1191 raise
1189 1192
1190 1193 def shellquote(s):
1191 1194 if os.sys.platform == 'OpenVMS':
1192 1195 return '"%s"' % s
1193 1196 else:
1194 1197 return "'%s'" % s.replace("'", "'\\''")
1195 1198
1196 1199 def quotecommand(cmd):
1197 1200 return cmd
1198 1201
1199 1202 def popen(command):
1200 1203 return os.popen(command)
1201 1204
1202 1205 def testpid(pid):
1203 1206 '''return False if pid dead, True if running or not sure'''
1204 1207 if os.sys.platform == 'OpenVMS':
1205 1208 return True
1206 1209 try:
1207 1210 os.kill(pid, 0)
1208 1211 return True
1209 1212 except OSError, inst:
1210 1213 return inst.errno != errno.ESRCH
1211 1214
1212 1215 def explain_exit(code):
1213 1216 """return a 2-tuple (desc, code) describing a process's status"""
1214 1217 if os.WIFEXITED(code):
1215 1218 val = os.WEXITSTATUS(code)
1216 1219 return _("exited with status %d") % val, val
1217 1220 elif os.WIFSIGNALED(code):
1218 1221 val = os.WTERMSIG(code)
1219 1222 return _("killed by signal %d") % val, val
1220 1223 elif os.WIFSTOPPED(code):
1221 1224 val = os.WSTOPSIG(code)
1222 1225 return _("stopped by signal %d") % val, val
1223 1226 raise ValueError(_("invalid exit code"))
1224 1227
1225 1228 def isowner(fp, st=None):
1226 1229 """Return True if the file object f belongs to the current user.
1227 1230
1228 1231 The return value of a util.fstat(f) may be passed as the st argument.
1229 1232 """
1230 1233 if st is None:
1231 1234 st = fstat(fp)
1232 1235 return st.st_uid == os.getuid()
1233 1236
1234 1237 def find_in_path(name, path, default=None):
1235 1238 '''find name in search path. path can be string (will be split
1236 1239 with os.pathsep), or iterable thing that returns strings. if name
1237 1240 found, return path to name. else return default.'''
1238 1241 if isinstance(path, str):
1239 1242 path = path.split(os.pathsep)
1240 1243 for p in path:
1241 1244 p_name = os.path.join(p, name)
1242 1245 if os.path.exists(p_name):
1243 1246 return p_name
1244 1247 return default
1245 1248
1246 1249 def set_signal_handler():
1247 1250 pass
1248 1251
1249 1252 def find_exe(name, default=None):
1250 1253 '''find path of an executable.
1251 1254 if name contains a path component, return it as is. otherwise,
1252 1255 use normal executable search path.'''
1253 1256
1254 1257 if os.sep in name or sys.platform == 'OpenVMS':
1255 1258 # don't check the executable bit. if the file isn't
1256 1259 # executable, whoever tries to actually run it will give a
1257 1260 # much more useful error message.
1258 1261 return name
1259 1262 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1260 1263
1261 1264 def _buildencodefun():
1262 1265 e = '_'
1263 1266 win_reserved = [ord(x) for x in '\\:*?"<>|']
1264 1267 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1265 1268 for x in (range(32) + range(126, 256) + win_reserved):
1266 1269 cmap[chr(x)] = "~%02x" % x
1267 1270 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1268 1271 cmap[chr(x)] = e + chr(x).lower()
1269 1272 dmap = {}
1270 1273 for k, v in cmap.iteritems():
1271 1274 dmap[v] = k
1272 1275 def decode(s):
1273 1276 i = 0
1274 1277 while i < len(s):
1275 1278 for l in xrange(1, 4):
1276 1279 try:
1277 1280 yield dmap[s[i:i+l]]
1278 1281 i += l
1279 1282 break
1280 1283 except KeyError:
1281 1284 pass
1282 1285 else:
1283 1286 raise KeyError
1284 1287 return (lambda s: "".join([cmap[c] for c in s]),
1285 1288 lambda s: "".join(list(decode(s))))
1286 1289
1287 1290 encodefilename, decodefilename = _buildencodefun()
1288 1291
1289 1292 def encodedopener(openerfn, fn):
1290 1293 def o(path, *args, **kw):
1291 1294 return openerfn(fn(path), *args, **kw)
1292 1295 return o
1293 1296
1294 1297 def mktempcopy(name, emptyok=False):
1295 1298 """Create a temporary file with the same contents from name
1296 1299
1297 1300 The permission bits are copied from the original file.
1298 1301
1299 1302 If the temporary file is going to be truncated immediately, you
1300 1303 can use emptyok=True as an optimization.
1301 1304
1302 1305 Returns the name of the temporary file.
1303 1306 """
1304 1307 d, fn = os.path.split(name)
1305 1308 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1306 1309 os.close(fd)
1307 1310 # Temporary files are created with mode 0600, which is usually not
1308 1311 # what we want. If the original file already exists, just copy
1309 1312 # its mode. Otherwise, manually obey umask.
1310 1313 try:
1311 1314 st_mode = os.lstat(name).st_mode & 0777
1312 1315 except OSError, inst:
1313 1316 if inst.errno != errno.ENOENT:
1314 1317 raise
1315 1318 st_mode = 0666 & ~_umask
1316 1319 os.chmod(temp, st_mode)
1317 1320 if emptyok:
1318 1321 return temp
1319 1322 try:
1320 1323 try:
1321 1324 ifp = posixfile(name, "rb")
1322 1325 except IOError, inst:
1323 1326 if inst.errno == errno.ENOENT:
1324 1327 return temp
1325 1328 if not getattr(inst, 'filename', None):
1326 1329 inst.filename = name
1327 1330 raise
1328 1331 ofp = posixfile(temp, "wb")
1329 1332 for chunk in filechunkiter(ifp):
1330 1333 ofp.write(chunk)
1331 1334 ifp.close()
1332 1335 ofp.close()
1333 1336 except:
1334 1337 try: os.unlink(temp)
1335 1338 except: pass
1336 1339 raise
1337 1340 return temp
1338 1341
1339 1342 class atomictempfile(posixfile):
1340 1343 """file-like object that atomically updates a file
1341 1344
1342 1345 All writes will be redirected to a temporary copy of the original
1343 1346 file. When rename is called, the copy is renamed to the original
1344 1347 name, making the changes visible.
1345 1348 """
1346 1349 def __init__(self, name, mode):
1347 1350 self.__name = name
1348 1351 self.temp = mktempcopy(name, emptyok=('w' in mode))
1349 1352 posixfile.__init__(self, self.temp, mode)
1350 1353
1351 1354 def rename(self):
1352 1355 if not self.closed:
1353 1356 posixfile.close(self)
1354 1357 rename(self.temp, localpath(self.__name))
1355 1358
1356 1359 def __del__(self):
1357 1360 if not self.closed:
1358 1361 try:
1359 1362 os.unlink(self.temp)
1360 1363 except: pass
1361 1364 posixfile.close(self)
1362 1365
1363 1366 class opener(object):
1364 1367 """Open files relative to a base directory
1365 1368
1366 1369 This class is used to hide the details of COW semantics and
1367 1370 remote file access from higher level code.
1368 1371 """
1369 1372 def __init__(self, base, audit=True):
1370 1373 self.base = base
1371 1374 if audit:
1372 1375 self.audit_path = path_auditor(base)
1373 1376 else:
1374 1377 self.audit_path = always
1375 1378
1376 1379 def __getattr__(self, name):
1377 1380 if name == '_can_symlink':
1378 1381 self._can_symlink = checklink(self.base)
1379 1382 return self._can_symlink
1380 1383 raise AttributeError(name)
1381 1384
1382 1385 def __call__(self, path, mode="r", text=False, atomictemp=False):
1383 1386 self.audit_path(path)
1384 1387 f = os.path.join(self.base, path)
1385 1388
1386 1389 if not text and "b" not in mode:
1387 1390 mode += "b" # for that other OS
1388 1391
1389 1392 if mode[0] != "r":
1390 1393 try:
1391 1394 nlink = nlinks(f)
1392 1395 except OSError:
1393 1396 nlink = 0
1394 1397 d = os.path.dirname(f)
1395 1398 if not os.path.isdir(d):
1396 1399 os.makedirs(d)
1397 1400 if atomictemp:
1398 1401 return atomictempfile(f, mode)
1399 1402 if nlink > 1:
1400 1403 rename(mktempcopy(f), f)
1401 1404 return posixfile(f, mode)
1402 1405
1403 1406 def symlink(self, src, dst):
1404 1407 self.audit_path(dst)
1405 1408 linkname = os.path.join(self.base, dst)
1406 1409 try:
1407 1410 os.unlink(linkname)
1408 1411 except OSError:
1409 1412 pass
1410 1413
1411 1414 dirname = os.path.dirname(linkname)
1412 1415 if not os.path.exists(dirname):
1413 1416 os.makedirs(dirname)
1414 1417
1415 1418 if self._can_symlink:
1416 1419 try:
1417 1420 os.symlink(src, linkname)
1418 1421 except OSError, err:
1419 1422 raise OSError(err.errno, _('could not symlink to %r: %s') %
1420 1423 (src, err.strerror), linkname)
1421 1424 else:
1422 1425 f = self(dst, "w")
1423 1426 f.write(src)
1424 1427 f.close()
1425 1428
1426 1429 class chunkbuffer(object):
1427 1430 """Allow arbitrary sized chunks of data to be efficiently read from an
1428 1431 iterator over chunks of arbitrary size."""
1429 1432
1430 1433 def __init__(self, in_iter):
1431 1434 """in_iter is the iterator that's iterating over the input chunks.
1432 1435 targetsize is how big a buffer to try to maintain."""
1433 1436 self.iter = iter(in_iter)
1434 1437 self.buf = ''
1435 1438 self.targetsize = 2**16
1436 1439
1437 1440 def read(self, l):
1438 1441 """Read L bytes of data from the iterator of chunks of data.
1439 1442 Returns less than L bytes if the iterator runs dry."""
1440 1443 if l > len(self.buf) and self.iter:
1441 1444 # Clamp to a multiple of self.targetsize
1442 1445 targetsize = max(l, self.targetsize)
1443 1446 collector = cStringIO.StringIO()
1444 1447 collector.write(self.buf)
1445 1448 collected = len(self.buf)
1446 1449 for chunk in self.iter:
1447 1450 collector.write(chunk)
1448 1451 collected += len(chunk)
1449 1452 if collected >= targetsize:
1450 1453 break
1451 1454 if collected < targetsize:
1452 1455 self.iter = False
1453 1456 self.buf = collector.getvalue()
1454 1457 if len(self.buf) == l:
1455 1458 s, self.buf = str(self.buf), ''
1456 1459 else:
1457 1460 s, self.buf = self.buf[:l], buffer(self.buf, l)
1458 1461 return s
1459 1462
1460 1463 def filechunkiter(f, size=65536, limit=None):
1461 1464 """Create a generator that produces the data in the file size
1462 1465 (default 65536) bytes at a time, up to optional limit (default is
1463 1466 to read all data). Chunks may be less than size bytes if the
1464 1467 chunk is the last chunk in the file, or the file is a socket or
1465 1468 some other type of file that sometimes reads less data than is
1466 1469 requested."""
1467 1470 assert size >= 0
1468 1471 assert limit is None or limit >= 0
1469 1472 while True:
1470 1473 if limit is None: nbytes = size
1471 1474 else: nbytes = min(limit, size)
1472 1475 s = nbytes and f.read(nbytes)
1473 1476 if not s: break
1474 1477 if limit: limit -= len(s)
1475 1478 yield s
1476 1479
1477 1480 def makedate():
1478 1481 lt = time.localtime()
1479 1482 if lt[8] == 1 and time.daylight:
1480 1483 tz = time.altzone
1481 1484 else:
1482 1485 tz = time.timezone
1483 1486 return time.mktime(lt), tz
1484 1487
1485 1488 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1486 1489 """represent a (unixtime, offset) tuple as a localized time.
1487 1490 unixtime is seconds since the epoch, and offset is the time zone's
1488 1491 number of seconds away from UTC. if timezone is false, do not
1489 1492 append time zone to string."""
1490 1493 t, tz = date or makedate()
1491 1494 s = time.strftime(format, time.gmtime(float(t) - tz))
1492 1495 if timezone:
1493 1496 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1494 1497 return s
1495 1498
1496 1499 def strdate(string, format, defaults=[]):
1497 1500 """parse a localized time string and return a (unixtime, offset) tuple.
1498 1501 if the string cannot be parsed, ValueError is raised."""
1499 1502 def timezone(string):
1500 1503 tz = string.split()[-1]
1501 1504 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1502 1505 tz = int(tz)
1503 1506 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1504 1507 return offset
1505 1508 if tz == "GMT" or tz == "UTC":
1506 1509 return 0
1507 1510 return None
1508 1511
1509 1512 # NOTE: unixtime = localunixtime + offset
1510 1513 offset, date = timezone(string), string
1511 1514 if offset != None:
1512 1515 date = " ".join(string.split()[:-1])
1513 1516
1514 1517 # add missing elements from defaults
1515 1518 for part in defaults:
1516 1519 found = [True for p in part if ("%"+p) in format]
1517 1520 if not found:
1518 1521 date += "@" + defaults[part]
1519 1522 format += "@%" + part[0]
1520 1523
1521 1524 timetuple = time.strptime(date, format)
1522 1525 localunixtime = int(calendar.timegm(timetuple))
1523 1526 if offset is None:
1524 1527 # local timezone
1525 1528 unixtime = int(time.mktime(timetuple))
1526 1529 offset = unixtime - localunixtime
1527 1530 else:
1528 1531 unixtime = localunixtime + offset
1529 1532 return unixtime, offset
1530 1533
1531 1534 def parsedate(string, formats=None, defaults=None):
1532 1535 """parse a localized time string and return a (unixtime, offset) tuple.
1533 1536 The date may be a "unixtime offset" string or in one of the specified
1534 1537 formats."""
1535 1538 if not string:
1536 1539 return 0, 0
1537 1540 if not formats:
1538 1541 formats = defaultdateformats
1539 1542 string = string.strip()
1540 1543 try:
1541 1544 when, offset = map(int, string.split(' '))
1542 1545 except ValueError:
1543 1546 # fill out defaults
1544 1547 if not defaults:
1545 1548 defaults = {}
1546 1549 now = makedate()
1547 1550 for part in "d mb yY HI M S".split():
1548 1551 if part not in defaults:
1549 1552 if part[0] in "HMS":
1550 1553 defaults[part] = "00"
1551 1554 elif part[0] in "dm":
1552 1555 defaults[part] = "1"
1553 1556 else:
1554 1557 defaults[part] = datestr(now, "%" + part[0], False)
1555 1558
1556 1559 for format in formats:
1557 1560 try:
1558 1561 when, offset = strdate(string, format, defaults)
1559 1562 except ValueError:
1560 1563 pass
1561 1564 else:
1562 1565 break
1563 1566 else:
1564 1567 raise Abort(_('invalid date: %r ') % string)
1565 1568 # validate explicit (probably user-specified) date and
1566 1569 # time zone offset. values must fit in signed 32 bits for
1567 1570 # current 32-bit linux runtimes. timezones go from UTC-12
1568 1571 # to UTC+14
1569 1572 if abs(when) > 0x7fffffff:
1570 1573 raise Abort(_('date exceeds 32 bits: %d') % when)
1571 1574 if offset < -50400 or offset > 43200:
1572 1575 raise Abort(_('impossible time zone offset: %d') % offset)
1573 1576 return when, offset
1574 1577
1575 1578 def matchdate(date):
1576 1579 """Return a function that matches a given date match specifier
1577 1580
1578 1581 Formats include:
1579 1582
1580 1583 '{date}' match a given date to the accuracy provided
1581 1584
1582 1585 '<{date}' on or before a given date
1583 1586
1584 1587 '>{date}' on or after a given date
1585 1588
1586 1589 """
1587 1590
1588 1591 def lower(date):
1589 1592 return parsedate(date, extendeddateformats)[0]
1590 1593
1591 1594 def upper(date):
1592 1595 d = dict(mb="12", HI="23", M="59", S="59")
1593 1596 for days in "31 30 29".split():
1594 1597 try:
1595 1598 d["d"] = days
1596 1599 return parsedate(date, extendeddateformats, d)[0]
1597 1600 except:
1598 1601 pass
1599 1602 d["d"] = "28"
1600 1603 return parsedate(date, extendeddateformats, d)[0]
1601 1604
1602 1605 if date[0] == "<":
1603 1606 when = upper(date[1:])
1604 1607 return lambda x: x <= when
1605 1608 elif date[0] == ">":
1606 1609 when = lower(date[1:])
1607 1610 return lambda x: x >= when
1608 1611 elif date[0] == "-":
1609 1612 try:
1610 1613 days = int(date[1:])
1611 1614 except ValueError:
1612 1615 raise Abort(_("invalid day spec: %s") % date[1:])
1613 1616 when = makedate()[0] - days * 3600 * 24
1614 1617 return lambda x: x >= when
1615 1618 elif " to " in date:
1616 1619 a, b = date.split(" to ")
1617 1620 start, stop = lower(a), upper(b)
1618 1621 return lambda x: x >= start and x <= stop
1619 1622 else:
1620 1623 start, stop = lower(date), upper(date)
1621 1624 return lambda x: x >= start and x <= stop
1622 1625
1623 1626 def shortuser(user):
1624 1627 """Return a short representation of a user name or email address."""
1625 1628 f = user.find('@')
1626 1629 if f >= 0:
1627 1630 user = user[:f]
1628 1631 f = user.find('<')
1629 1632 if f >= 0:
1630 1633 user = user[f+1:]
1631 1634 f = user.find(' ')
1632 1635 if f >= 0:
1633 1636 user = user[:f]
1634 1637 f = user.find('.')
1635 1638 if f >= 0:
1636 1639 user = user[:f]
1637 1640 return user
1638 1641
1639 1642 def email(author):
1640 1643 '''get email of author.'''
1641 1644 r = author.find('>')
1642 1645 if r == -1: r = None
1643 1646 return author[author.find('<')+1:r]
1644 1647
1645 1648 def ellipsis(text, maxlength=400):
1646 1649 """Trim string to at most maxlength (default: 400) characters."""
1647 1650 if len(text) <= maxlength:
1648 1651 return text
1649 1652 else:
1650 1653 return "%s..." % (text[:maxlength-3])
1651 1654
1652 1655 def walkrepos(path):
1653 1656 '''yield every hg repository under path, recursively.'''
1654 1657 def errhandler(err):
1655 1658 if err.filename == path:
1656 1659 raise err
1657 1660
1658 1661 for root, dirs, files in os.walk(path, onerror=errhandler):
1659 1662 for d in dirs:
1660 1663 if d == '.hg':
1661 1664 yield root
1662 1665 dirs[:] = []
1663 1666 break
1664 1667
1665 1668 _rcpath = None
1666 1669
1667 1670 def os_rcpath():
1668 1671 '''return default os-specific hgrc search path'''
1669 1672 path = system_rcpath()
1670 1673 path.extend(user_rcpath())
1671 1674 path = [os.path.normpath(f) for f in path]
1672 1675 return path
1673 1676
1674 1677 def rcpath():
1675 1678 '''return hgrc search path. if env var HGRCPATH is set, use it.
1676 1679 for each item in path, if directory, use files ending in .rc,
1677 1680 else use item.
1678 1681 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1679 1682 if no HGRCPATH, use default os-specific path.'''
1680 1683 global _rcpath
1681 1684 if _rcpath is None:
1682 1685 if 'HGRCPATH' in os.environ:
1683 1686 _rcpath = []
1684 1687 for p in os.environ['HGRCPATH'].split(os.pathsep):
1685 1688 if not p: continue
1686 1689 if os.path.isdir(p):
1687 1690 for f, kind in osutil.listdir(p):
1688 1691 if f.endswith('.rc'):
1689 1692 _rcpath.append(os.path.join(p, f))
1690 1693 else:
1691 1694 _rcpath.append(p)
1692 1695 else:
1693 1696 _rcpath = os_rcpath()
1694 1697 return _rcpath
1695 1698
1696 1699 def bytecount(nbytes):
1697 1700 '''return byte count formatted as readable string, with units'''
1698 1701
1699 1702 units = (
1700 1703 (100, 1<<30, _('%.0f GB')),
1701 1704 (10, 1<<30, _('%.1f GB')),
1702 1705 (1, 1<<30, _('%.2f GB')),
1703 1706 (100, 1<<20, _('%.0f MB')),
1704 1707 (10, 1<<20, _('%.1f MB')),
1705 1708 (1, 1<<20, _('%.2f MB')),
1706 1709 (100, 1<<10, _('%.0f KB')),
1707 1710 (10, 1<<10, _('%.1f KB')),
1708 1711 (1, 1<<10, _('%.2f KB')),
1709 1712 (1, 1, _('%.0f bytes')),
1710 1713 )
1711 1714
1712 1715 for multiplier, divisor, format in units:
1713 1716 if nbytes >= divisor * multiplier:
1714 1717 return format % (nbytes / float(divisor))
1715 1718 return units[-1][2] % nbytes
1716 1719
1717 1720 def drop_scheme(scheme, path):
1718 1721 sc = scheme + ':'
1719 1722 if path.startswith(sc):
1720 1723 path = path[len(sc):]
1721 1724 if path.startswith('//'):
1722 1725 path = path[2:]
1723 1726 return path
1724 1727
1725 1728 def uirepr(s):
1726 1729 # Avoid double backslash in Windows path repr()
1727 1730 return repr(s).replace('\\\\', '\\')
1728 1731
1729 1732 def hidepassword(url):
1730 1733 '''hide user credential in a url string'''
1731 1734 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
1732 1735 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
1733 1736 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
1734 1737
1735 1738 def removeauth(url):
1736 1739 '''remove all authentication information from a url string'''
1737 1740 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
1738 1741 netloc = netloc[netloc.find('@')+1:]
1739 1742 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
@@ -1,340 +1,371 b''
1 1 # util_win32.py - utility functions that use win32 API
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of
7 7 # the GNU General Public License, incorporated herein by reference.
8 8
9 9 # Mark Hammond's win32all package allows better functionality on
10 10 # Windows. this module overrides definitions in util.py. if not
11 11 # available, import of this module will fail, and generic code will be
12 12 # used.
13 13
14 14 import win32api
15 15
16 16 from i18n import _
17 17 import errno, os, pywintypes, win32con, win32file, win32process
18 18 import cStringIO, winerror
19 19 import osutil
20 20 from win32com.shell import shell,shellcon
21 21
22 22 class WinError:
23 23 winerror_map = {
24 24 winerror.ERROR_ACCESS_DENIED: errno.EACCES,
25 25 winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES,
26 26 winerror.ERROR_ACCOUNT_RESTRICTION: errno.EACCES,
27 27 winerror.ERROR_ALREADY_ASSIGNED: errno.EBUSY,
28 28 winerror.ERROR_ALREADY_EXISTS: errno.EEXIST,
29 29 winerror.ERROR_ARITHMETIC_OVERFLOW: errno.ERANGE,
30 30 winerror.ERROR_BAD_COMMAND: errno.EIO,
31 31 winerror.ERROR_BAD_DEVICE: errno.ENODEV,
32 32 winerror.ERROR_BAD_DRIVER_LEVEL: errno.ENXIO,
33 33 winerror.ERROR_BAD_EXE_FORMAT: errno.ENOEXEC,
34 34 winerror.ERROR_BAD_FORMAT: errno.ENOEXEC,
35 35 winerror.ERROR_BAD_LENGTH: errno.EINVAL,
36 36 winerror.ERROR_BAD_PATHNAME: errno.ENOENT,
37 37 winerror.ERROR_BAD_PIPE: errno.EPIPE,
38 38 winerror.ERROR_BAD_UNIT: errno.ENODEV,
39 39 winerror.ERROR_BAD_USERNAME: errno.EINVAL,
40 40 winerror.ERROR_BROKEN_PIPE: errno.EPIPE,
41 41 winerror.ERROR_BUFFER_OVERFLOW: errno.ENAMETOOLONG,
42 42 winerror.ERROR_BUSY: errno.EBUSY,
43 43 winerror.ERROR_BUSY_DRIVE: errno.EBUSY,
44 44 winerror.ERROR_CALL_NOT_IMPLEMENTED: errno.ENOSYS,
45 45 winerror.ERROR_CANNOT_MAKE: errno.EACCES,
46 46 winerror.ERROR_CANTOPEN: errno.EIO,
47 47 winerror.ERROR_CANTREAD: errno.EIO,
48 48 winerror.ERROR_CANTWRITE: errno.EIO,
49 49 winerror.ERROR_CRC: errno.EIO,
50 50 winerror.ERROR_CURRENT_DIRECTORY: errno.EACCES,
51 51 winerror.ERROR_DEVICE_IN_USE: errno.EBUSY,
52 52 winerror.ERROR_DEV_NOT_EXIST: errno.ENODEV,
53 53 winerror.ERROR_DIRECTORY: errno.EINVAL,
54 54 winerror.ERROR_DIR_NOT_EMPTY: errno.ENOTEMPTY,
55 55 winerror.ERROR_DISK_CHANGE: errno.EIO,
56 56 winerror.ERROR_DISK_FULL: errno.ENOSPC,
57 57 winerror.ERROR_DRIVE_LOCKED: errno.EBUSY,
58 58 winerror.ERROR_ENVVAR_NOT_FOUND: errno.EINVAL,
59 59 winerror.ERROR_EXE_MARKED_INVALID: errno.ENOEXEC,
60 60 winerror.ERROR_FILENAME_EXCED_RANGE: errno.ENAMETOOLONG,
61 61 winerror.ERROR_FILE_EXISTS: errno.EEXIST,
62 62 winerror.ERROR_FILE_INVALID: errno.ENODEV,
63 63 winerror.ERROR_FILE_NOT_FOUND: errno.ENOENT,
64 64 winerror.ERROR_GEN_FAILURE: errno.EIO,
65 65 winerror.ERROR_HANDLE_DISK_FULL: errno.ENOSPC,
66 66 winerror.ERROR_INSUFFICIENT_BUFFER: errno.ENOMEM,
67 67 winerror.ERROR_INVALID_ACCESS: errno.EACCES,
68 68 winerror.ERROR_INVALID_ADDRESS: errno.EFAULT,
69 69 winerror.ERROR_INVALID_BLOCK: errno.EFAULT,
70 70 winerror.ERROR_INVALID_DATA: errno.EINVAL,
71 71 winerror.ERROR_INVALID_DRIVE: errno.ENODEV,
72 72 winerror.ERROR_INVALID_EXE_SIGNATURE: errno.ENOEXEC,
73 73 winerror.ERROR_INVALID_FLAGS: errno.EINVAL,
74 74 winerror.ERROR_INVALID_FUNCTION: errno.ENOSYS,
75 75 winerror.ERROR_INVALID_HANDLE: errno.EBADF,
76 76 winerror.ERROR_INVALID_LOGON_HOURS: errno.EACCES,
77 77 winerror.ERROR_INVALID_NAME: errno.EINVAL,
78 78 winerror.ERROR_INVALID_OWNER: errno.EINVAL,
79 79 winerror.ERROR_INVALID_PARAMETER: errno.EINVAL,
80 80 winerror.ERROR_INVALID_PASSWORD: errno.EPERM,
81 81 winerror.ERROR_INVALID_PRIMARY_GROUP: errno.EINVAL,
82 82 winerror.ERROR_INVALID_SIGNAL_NUMBER: errno.EINVAL,
83 83 winerror.ERROR_INVALID_TARGET_HANDLE: errno.EIO,
84 84 winerror.ERROR_INVALID_WORKSTATION: errno.EACCES,
85 85 winerror.ERROR_IO_DEVICE: errno.EIO,
86 86 winerror.ERROR_IO_INCOMPLETE: errno.EINTR,
87 87 winerror.ERROR_LOCKED: errno.EBUSY,
88 88 winerror.ERROR_LOCK_VIOLATION: errno.EACCES,
89 89 winerror.ERROR_LOGON_FAILURE: errno.EACCES,
90 90 winerror.ERROR_MAPPED_ALIGNMENT: errno.EINVAL,
91 91 winerror.ERROR_META_EXPANSION_TOO_LONG: errno.E2BIG,
92 92 winerror.ERROR_MORE_DATA: errno.EPIPE,
93 93 winerror.ERROR_NEGATIVE_SEEK: errno.ESPIPE,
94 94 winerror.ERROR_NOACCESS: errno.EFAULT,
95 95 winerror.ERROR_NONE_MAPPED: errno.EINVAL,
96 96 winerror.ERROR_NOT_ENOUGH_MEMORY: errno.ENOMEM,
97 97 winerror.ERROR_NOT_READY: errno.EAGAIN,
98 98 winerror.ERROR_NOT_SAME_DEVICE: errno.EXDEV,
99 99 winerror.ERROR_NO_DATA: errno.EPIPE,
100 100 winerror.ERROR_NO_MORE_SEARCH_HANDLES: errno.EIO,
101 101 winerror.ERROR_NO_PROC_SLOTS: errno.EAGAIN,
102 102 winerror.ERROR_NO_SUCH_PRIVILEGE: errno.EACCES,
103 103 winerror.ERROR_OPEN_FAILED: errno.EIO,
104 104 winerror.ERROR_OPEN_FILES: errno.EBUSY,
105 105 winerror.ERROR_OPERATION_ABORTED: errno.EINTR,
106 106 winerror.ERROR_OUTOFMEMORY: errno.ENOMEM,
107 107 winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES,
108 108 winerror.ERROR_PATH_BUSY: errno.EBUSY,
109 109 winerror.ERROR_PATH_NOT_FOUND: errno.ENOENT,
110 110 winerror.ERROR_PIPE_BUSY: errno.EBUSY,
111 111 winerror.ERROR_PIPE_CONNECTED: errno.EPIPE,
112 112 winerror.ERROR_PIPE_LISTENING: errno.EPIPE,
113 113 winerror.ERROR_PIPE_NOT_CONNECTED: errno.EPIPE,
114 114 winerror.ERROR_PRIVILEGE_NOT_HELD: errno.EACCES,
115 115 winerror.ERROR_READ_FAULT: errno.EIO,
116 116 winerror.ERROR_SEEK: errno.EIO,
117 117 winerror.ERROR_SEEK_ON_DEVICE: errno.ESPIPE,
118 118 winerror.ERROR_SHARING_BUFFER_EXCEEDED: errno.ENFILE,
119 119 winerror.ERROR_SHARING_VIOLATION: errno.EACCES,
120 120 winerror.ERROR_STACK_OVERFLOW: errno.ENOMEM,
121 121 winerror.ERROR_SWAPERROR: errno.ENOENT,
122 122 winerror.ERROR_TOO_MANY_MODULES: errno.EMFILE,
123 123 winerror.ERROR_TOO_MANY_OPEN_FILES: errno.EMFILE,
124 124 winerror.ERROR_UNRECOGNIZED_MEDIA: errno.ENXIO,
125 125 winerror.ERROR_UNRECOGNIZED_VOLUME: errno.ENODEV,
126 126 winerror.ERROR_WAIT_NO_CHILDREN: errno.ECHILD,
127 127 winerror.ERROR_WRITE_FAULT: errno.EIO,
128 128 winerror.ERROR_WRITE_PROTECT: errno.EROFS,
129 129 }
130 130
131 131 def __init__(self, err):
132 132 self.win_errno, self.win_function, self.win_strerror = err
133 133 if self.win_strerror.endswith('.'):
134 134 self.win_strerror = self.win_strerror[:-1]
135 135
136 136 class WinIOError(WinError, IOError):
137 137 def __init__(self, err, filename=None):
138 138 WinError.__init__(self, err)
139 139 IOError.__init__(self, self.winerror_map.get(self.win_errno, 0),
140 140 self.win_strerror)
141 141 self.filename = filename
142 142
143 143 class WinOSError(WinError, OSError):
144 144 def __init__(self, err):
145 145 WinError.__init__(self, err)
146 146 OSError.__init__(self, self.winerror_map.get(self.win_errno, 0),
147 147 self.win_strerror)
148 148
149 149 def os_link(src, dst):
150 150 try:
151 151 win32file.CreateHardLink(dst, src)
152 152 # CreateHardLink sometimes succeeds on mapped drives but
153 153 # following nlinks() returns 1. Check it now and bail out.
154 154 if nlinks(src) < 2:
155 155 try:
156 156 win32file.DeleteFile(dst)
157 157 except:
158 158 pass
159 159 # Fake hardlinking error
160 160 raise WinOSError((18, 'CreateHardLink', 'The system cannot '
161 161 'move the file to a different disk drive'))
162 162 except pywintypes.error, details:
163 163 raise WinOSError(details)
164 164
165 165 def nlinks(pathname):
166 166 """Return number of hardlinks for the given file."""
167 167 try:
168 168 fh = win32file.CreateFile(pathname,
169 169 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
170 170 None, win32file.OPEN_EXISTING, 0, None)
171 171 res = win32file.GetFileInformationByHandle(fh)
172 172 fh.Close()
173 173 return res[7]
174 174 except pywintypes.error:
175 175 return os.lstat(pathname).st_nlink
176 176
177 177 def testpid(pid):
178 178 '''return True if pid is still running or unable to
179 179 determine, False otherwise'''
180 180 try:
181 181 handle = win32api.OpenProcess(
182 182 win32con.PROCESS_QUERY_INFORMATION, False, pid)
183 183 if handle:
184 184 status = win32process.GetExitCodeProcess(handle)
185 185 return status == win32con.STILL_ACTIVE
186 186 except pywintypes.error, details:
187 187 return details[0] != winerror.ERROR_INVALID_PARAMETER
188 188 return True
189 189
190 def lookup_reg(key, valname=None, scope=None):
191 ''' Look up a key/value name in the Windows registry.
192
193 valname: value name. If unspecified, the default value for the key
194 is used.
195 scope: optionally specify scope for registry lookup, this can be
196 a sequence of scopes to look up in order. Default (CURRENT_USER,
197 LOCAL_MACHINE).
198 '''
199 try:
200 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \
201 QueryValueEx, OpenKey
202 except ImportError:
203 return None
204
205 def query_val(scope, key):
206 try:
207 keyhandle = OpenKey(scope, key)
208 return QueryValueEx(keyhandle, valname)[0]
209 except EnvironmentError:
210 return None
211
212 if scope is None:
213 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
214 elif not isinstance(scope, (list, tuple)):
215 scope = (scope,)
216 for s in scope:
217 val = query_val(s, key, valname)
218 if val is not None:
219 return val
220
190 221 def system_rcpath_win32():
191 222 '''return default os-specific hgrc search path'''
192 223 proc = win32api.GetCurrentProcess()
193 224 try:
194 225 # This will fail on windows < NT
195 226 filename = win32process.GetModuleFileNameEx(proc, 0)
196 227 except:
197 228 filename = win32api.GetModuleFileName(0)
198 229 # Use mercurial.ini found in directory with hg.exe
199 230 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
200 231 if os.path.isfile(progrc):
201 232 return [progrc]
202 233 # else look for a system rcpath in the registry
203 234 try:
204 235 value = win32api.RegQueryValue(
205 236 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
206 237 rcpath = []
207 238 for p in value.split(os.pathsep):
208 239 if p.lower().endswith('mercurial.ini'):
209 240 rcpath.append(p)
210 241 elif os.path.isdir(p):
211 242 for f, kind in osutil.listdir(p):
212 243 if f.endswith('.rc'):
213 244 rcpath.append(os.path.join(p, f))
214 245 return rcpath
215 246 except pywintypes.error:
216 247 return []
217 248
218 249 def user_rcpath_win32():
219 250 '''return os-specific hgrc search path to the user dir'''
220 251 userdir = os.path.expanduser('~')
221 252 if sys.getwindowsversion() != 2 and userdir == '~':
222 253 # We are on win < nt: fetch the APPDATA directory location and use
223 254 # the parent directory as the user home dir.
224 255 appdir = shell.SHGetPathFromIDList(
225 256 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
226 257 userdir = os.path.dirname(appdir)
227 258 return os.path.join(userdir, 'mercurial.ini')
228 259
229 260 class posixfile_nt(object):
230 261 '''file object with posix-like semantics. on windows, normal
231 262 files can not be deleted or renamed if they are open. must open
232 263 with win32file.FILE_SHARE_DELETE. this flag does not exist on
233 264 windows < nt, so do not use this class there.'''
234 265
235 266 # tried to use win32file._open_osfhandle to pass fd to os.fdopen,
236 267 # but does not work at all. wrap win32 file api instead.
237 268
238 269 def __init__(self, name, mode='rb'):
239 270 self.closed = False
240 271 self.name = name
241 272 self.mode = mode
242 273 access = 0
243 274 if 'r' in mode or '+' in mode:
244 275 access |= win32file.GENERIC_READ
245 276 if 'w' in mode or 'a' in mode or '+' in mode:
246 277 access |= win32file.GENERIC_WRITE
247 278 if 'r' in mode:
248 279 creation = win32file.OPEN_EXISTING
249 280 elif 'a' in mode:
250 281 creation = win32file.OPEN_ALWAYS
251 282 else:
252 283 creation = win32file.CREATE_ALWAYS
253 284 try:
254 285 self.handle = win32file.CreateFile(name,
255 286 access,
256 287 win32file.FILE_SHARE_READ |
257 288 win32file.FILE_SHARE_WRITE |
258 289 win32file.FILE_SHARE_DELETE,
259 290 None,
260 291 creation,
261 292 win32file.FILE_ATTRIBUTE_NORMAL,
262 293 0)
263 294 except pywintypes.error, err:
264 295 raise WinIOError(err, name)
265 296
266 297 def __iter__(self):
267 298 for line in self.read().splitlines(True):
268 299 yield line
269 300
270 301 def read(self, count=-1):
271 302 try:
272 303 cs = cStringIO.StringIO()
273 304 while count:
274 305 wincount = int(count)
275 306 if wincount == -1:
276 307 wincount = 1048576
277 308 val, data = win32file.ReadFile(self.handle, wincount)
278 309 if not data: break
279 310 cs.write(data)
280 311 if count != -1:
281 312 count -= len(data)
282 313 return cs.getvalue()
283 314 except pywintypes.error, err:
284 315 raise WinIOError(err)
285 316
286 317 def write(self, data):
287 318 try:
288 319 if 'a' in self.mode:
289 320 win32file.SetFilePointer(self.handle, 0, win32file.FILE_END)
290 321 nwrit = 0
291 322 while nwrit < len(data):
292 323 val, nwrit = win32file.WriteFile(self.handle, data)
293 324 data = data[nwrit:]
294 325 except pywintypes.error, err:
295 326 raise WinIOError(err)
296 327
297 328 def writelines(self, sequence):
298 329 for s in sequence:
299 330 self.write(s)
300 331
301 332 def seek(self, pos, whence=0):
302 333 try:
303 334 win32file.SetFilePointer(self.handle, int(pos), whence)
304 335 except pywintypes.error, err:
305 336 raise WinIOError(err)
306 337
307 338 def tell(self):
308 339 try:
309 340 return win32file.SetFilePointer(self.handle, 0,
310 341 win32file.FILE_CURRENT)
311 342 except pywintypes.error, err:
312 343 raise WinIOError(err)
313 344
314 345 def close(self):
315 346 if not self.closed:
316 347 self.handle = None
317 348 self.closed = True
318 349
319 350 def flush(self):
320 351 # we have no application-level buffering
321 352 pass
322 353
323 354 def truncate(self, pos=0):
324 355 try:
325 356 win32file.SetFilePointer(self.handle, int(pos),
326 357 win32file.FILE_BEGIN)
327 358 win32file.SetEndOfFile(self.handle)
328 359 except pywintypes.error, err:
329 360 raise WinIOError(err)
330 361
331 362 getuser_fallback = win32api.GetUserName
332 363
333 364 def set_signal_handler_win32():
334 365 """Register a termination handler for console events including
335 366 CTRL+C. python signal handlers do not work well with socket
336 367 operations.
337 368 """
338 369 def handler(event):
339 370 win32process.ExitProcess(1)
340 371 win32api.SetConsoleCtrlHandler(handler)
General Comments 0
You need to be logged in to leave comments. Login now