##// END OF EJS Templates
util: move "default" hidewindow to posix.py...
Adrian Buehlmann -
r14911:5b395031 default
parent child Browse files
Show More
@@ -1,342 +1,350
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, errno, stat, getpass, pwd, grp, tempfile
10 10
11 11 posixfile = open
12 12 nulldev = '/dev/null'
13 13 normpath = os.path.normpath
14 14 samestat = os.path.samestat
15 15 oslink = os.link
16 16 unlink = os.unlink
17 17 rename = os.rename
18 18 expandglobs = False
19 19
20 20 umask = os.umask(0)
21 21 os.umask(umask)
22 22
23 23 def openhardlinks():
24 24 '''return true if it is safe to hold open file handles to hardlinks'''
25 25 return True
26 26
27 27 def nlinks(name):
28 28 '''return number of hardlinks for the given file'''
29 29 return os.lstat(name).st_nlink
30 30
31 31 def parsepatchoutput(output_line):
32 32 """parses the output produced by patch and returns the filename"""
33 33 pf = output_line[14:]
34 34 if os.sys.platform == 'OpenVMS':
35 35 if pf[0] == '`':
36 36 pf = pf[1:-1] # Remove the quotes
37 37 else:
38 38 if pf.startswith("'") and pf.endswith("'") and " " in pf:
39 39 pf = pf[1:-1] # Remove the quotes
40 40 return pf
41 41
42 42 def sshargs(sshcmd, host, user, port):
43 43 '''Build argument list for ssh'''
44 44 args = user and ("%s@%s" % (user, host)) or host
45 45 return port and ("%s -p %s" % (args, port)) or args
46 46
47 47 def isexec(f):
48 48 """check whether a file is executable"""
49 49 return (os.lstat(f).st_mode & 0100 != 0)
50 50
51 51 def setflags(f, l, x):
52 52 s = os.lstat(f).st_mode
53 53 if l:
54 54 if not stat.S_ISLNK(s):
55 55 # switch file to link
56 56 fp = open(f)
57 57 data = fp.read()
58 58 fp.close()
59 59 os.unlink(f)
60 60 try:
61 61 os.symlink(data, f)
62 62 except OSError:
63 63 # failed to make a link, rewrite file
64 64 fp = open(f, "w")
65 65 fp.write(data)
66 66 fp.close()
67 67 # no chmod needed at this point
68 68 return
69 69 if stat.S_ISLNK(s):
70 70 # switch link to file
71 71 data = os.readlink(f)
72 72 os.unlink(f)
73 73 fp = open(f, "w")
74 74 fp.write(data)
75 75 fp.close()
76 76 s = 0666 & ~umask # avoid restatting for chmod
77 77
78 78 sx = s & 0100
79 79 if x and not sx:
80 80 # Turn on +x for every +r bit when making a file executable
81 81 # and obey umask.
82 82 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
83 83 elif not x and sx:
84 84 # Turn off all +x bits
85 85 os.chmod(f, s & 0666)
86 86
87 87 def checkexec(path):
88 88 """
89 89 Check whether the given path is on a filesystem with UNIX-like exec flags
90 90
91 91 Requires a directory (like /foo/.hg)
92 92 """
93 93
94 94 # VFAT on some Linux versions can flip mode but it doesn't persist
95 95 # a FS remount. Frequently we can detect it if files are created
96 96 # with exec bit on.
97 97
98 98 try:
99 99 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
100 100 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
101 101 try:
102 102 os.close(fh)
103 103 m = os.stat(fn).st_mode & 0777
104 104 new_file_has_exec = m & EXECFLAGS
105 105 os.chmod(fn, m ^ EXECFLAGS)
106 106 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
107 107 finally:
108 108 os.unlink(fn)
109 109 except (IOError, OSError):
110 110 # we don't care, the user probably won't be able to commit anyway
111 111 return False
112 112 return not (new_file_has_exec or exec_flags_cannot_flip)
113 113
114 114 def checklink(path):
115 115 """check whether the given path is on a symlink-capable filesystem"""
116 116 # mktemp is not racy because symlink creation will fail if the
117 117 # file already exists
118 118 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
119 119 try:
120 120 os.symlink(".", name)
121 121 os.unlink(name)
122 122 return True
123 123 except (OSError, AttributeError):
124 124 return False
125 125
126 126 def checkosfilename(path):
127 127 '''Check that the base-relative path is a valid filename on this platform.
128 128 Returns None if the path is ok, or a UI string describing the problem.'''
129 129 pass # on posix platforms, every path is ok
130 130
131 131 def setbinary(fd):
132 132 pass
133 133
134 134 def pconvert(path):
135 135 return path
136 136
137 137 def localpath(path):
138 138 return path
139 139
140 140 def samefile(fpath1, fpath2):
141 141 """Returns whether path1 and path2 refer to the same file. This is only
142 142 guaranteed to work for files, not directories."""
143 143 return os.path.samefile(fpath1, fpath2)
144 144
145 145 def samedevice(fpath1, fpath2):
146 146 """Returns whether fpath1 and fpath2 are on the same device. This is only
147 147 guaranteed to work for files, not directories."""
148 148 st1 = os.lstat(fpath1)
149 149 st2 = os.lstat(fpath2)
150 150 return st1.st_dev == st2.st_dev
151 151
152 152 if sys.platform == 'darwin':
153 153 import fcntl # only needed on darwin, missing on jython
154 154 def realpath(path):
155 155 '''
156 156 Returns the true, canonical file system path equivalent to the given
157 157 path.
158 158
159 159 Equivalent means, in this case, resulting in the same, unique
160 160 file system link to the path. Every file system entry, whether a file,
161 161 directory, hard link or symbolic link or special, will have a single
162 162 path preferred by the system, but may allow multiple, differing path
163 163 lookups to point to it.
164 164
165 165 Most regular UNIX file systems only allow a file system entry to be
166 166 looked up by its distinct path. Obviously, this does not apply to case
167 167 insensitive file systems, whether case preserving or not. The most
168 168 complex issue to deal with is file systems transparently reencoding the
169 169 path, such as the non-standard Unicode normalisation required for HFS+
170 170 and HFSX.
171 171 '''
172 172 # Constants copied from /usr/include/sys/fcntl.h
173 173 F_GETPATH = 50
174 174 O_SYMLINK = 0x200000
175 175
176 176 try:
177 177 fd = os.open(path, O_SYMLINK)
178 178 except OSError, err:
179 179 if err.errno == errno.ENOENT:
180 180 return path
181 181 raise
182 182
183 183 try:
184 184 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
185 185 finally:
186 186 os.close(fd)
187 187 else:
188 188 # Fallback to the likely inadequate Python builtin function.
189 189 realpath = os.path.realpath
190 190
191 191 def shellquote(s):
192 192 if os.sys.platform == 'OpenVMS':
193 193 return '"%s"' % s
194 194 else:
195 195 return "'%s'" % s.replace("'", "'\\''")
196 196
197 197 def quotecommand(cmd):
198 198 return cmd
199 199
200 200 def popen(command, mode='r'):
201 201 return os.popen(command, mode)
202 202
203 203 def testpid(pid):
204 204 '''return False if pid dead, True if running or not sure'''
205 205 if os.sys.platform == 'OpenVMS':
206 206 return True
207 207 try:
208 208 os.kill(pid, 0)
209 209 return True
210 210 except OSError, inst:
211 211 return inst.errno != errno.ESRCH
212 212
213 213 def explainexit(code):
214 214 """return a 2-tuple (desc, code) describing a subprocess status
215 215 (codes from kill are negative - not os.system/wait encoding)"""
216 216 if code >= 0:
217 217 return _("exited with status %d") % code, code
218 218 return _("killed by signal %d") % -code, -code
219 219
220 220 def isowner(st):
221 221 """Return True if the stat object st is from the current user."""
222 222 return st.st_uid == os.getuid()
223 223
224 224 def findexe(command):
225 225 '''Find executable for command searching like which does.
226 226 If command is a basename then PATH is searched for command.
227 227 PATH isn't searched if command is an absolute or relative path.
228 228 If command isn't found None is returned.'''
229 229 if sys.platform == 'OpenVMS':
230 230 return command
231 231
232 232 def findexisting(executable):
233 233 'Will return executable if existing file'
234 234 if os.path.exists(executable):
235 235 return executable
236 236 return None
237 237
238 238 if os.sep in command:
239 239 return findexisting(command)
240 240
241 241 for path in os.environ.get('PATH', '').split(os.pathsep):
242 242 executable = findexisting(os.path.join(path, command))
243 243 if executable is not None:
244 244 return executable
245 245 return None
246 246
247 247 def setsignalhandler():
248 248 pass
249 249
250 250 def statfiles(files):
251 251 'Stat each file in files and yield stat or None if file does not exist.'
252 252 lstat = os.lstat
253 253 for nf in files:
254 254 try:
255 255 st = lstat(nf)
256 256 except OSError, err:
257 257 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
258 258 raise
259 259 st = None
260 260 yield st
261 261
262 262 def getuser():
263 263 '''return name of current user'''
264 264 return getpass.getuser()
265 265
266 266 def username(uid=None):
267 267 """Return the name of the user with the given uid.
268 268
269 269 If uid is None, return the name of the current user."""
270 270
271 271 if uid is None:
272 272 uid = os.getuid()
273 273 try:
274 274 return pwd.getpwuid(uid)[0]
275 275 except KeyError:
276 276 return str(uid)
277 277
278 278 def groupname(gid=None):
279 279 """Return the name of the group with the given gid.
280 280
281 281 If gid is None, return the name of the current group."""
282 282
283 283 if gid is None:
284 284 gid = os.getgid()
285 285 try:
286 286 return grp.getgrgid(gid)[0]
287 287 except KeyError:
288 288 return str(gid)
289 289
290 290 def groupmembers(name):
291 291 """Return the list of members of the group with the given
292 292 name, KeyError if the group does not exist.
293 293 """
294 294 return list(grp.getgrnam(name).gr_mem)
295 295
296 296 def spawndetached(args):
297 297 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
298 298 args[0], args)
299 299
300 300 def gethgcmd():
301 301 return sys.argv[:1]
302 302
303 303 def termwidth():
304 304 try:
305 305 import termios, array, fcntl
306 306 for dev in (sys.stderr, sys.stdout, sys.stdin):
307 307 try:
308 308 try:
309 309 fd = dev.fileno()
310 310 except AttributeError:
311 311 continue
312 312 if not os.isatty(fd):
313 313 continue
314 314 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
315 315 width = array.array('h', arri)[1]
316 316 if width > 0:
317 317 return width
318 318 except ValueError:
319 319 pass
320 320 except IOError, e:
321 321 if e[0] == errno.EINVAL:
322 322 pass
323 323 else:
324 324 raise
325 325 except ImportError:
326 326 pass
327 327 return 80
328 328
329 329 def makedir(path, notindexed):
330 330 os.mkdir(path)
331 331
332 332 def unlinkpath(f):
333 333 """unlink and remove the directory if it is empty"""
334 334 os.unlink(f)
335 335 # try removing directories that might now be empty
336 336 try:
337 337 os.removedirs(os.path.dirname(f))
338 338 except OSError:
339 339 pass
340 340
341 341 def lookupreg(key, name=None, scope=None):
342 342 return None
343
344 def hidewindow():
345 """Hide current shell window.
346
347 Used to hide the window opened when starting asynchronous
348 child process under Windows, unneeded on other systems.
349 """
350 pass
@@ -1,1597 +1,1589
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, calendar, textwrap, unicodedata, signal
20 20 import imp, socket, urllib
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 if sys.version_info >= (2, 5):
32 32 from hashlib import sha1 as _sha1
33 33 else:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import __builtin__
40 40
41 41 if sys.version_info[0] < 3:
42 42 def fakebuffer(sliceable, offset=0):
43 43 return sliceable[offset:]
44 44 else:
45 45 def fakebuffer(sliceable, offset=0):
46 46 return memoryview(sliceable)[offset:]
47 47 try:
48 48 buffer
49 49 except NameError:
50 50 __builtin__.buffer = fakebuffer
51 51
52 52 import subprocess
53 53 closefds = os.name == 'posix'
54 54
55 55 def popen2(cmd, env=None, newlines=False):
56 56 # Setting bufsize to -1 lets the system decide the buffer size.
57 57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 60 close_fds=closefds,
61 61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 62 universal_newlines=newlines,
63 63 env=env)
64 64 return p.stdin, p.stdout
65 65
66 66 def popen3(cmd, env=None, newlines=False):
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.PIPE,
71 71 universal_newlines=newlines,
72 72 env=env)
73 73 return p.stdin, p.stdout, p.stderr
74 74
75 75 def version():
76 76 """Return version information if available."""
77 77 try:
78 78 import __version__
79 79 return __version__.version
80 80 except ImportError:
81 81 return 'unknown'
82 82
83 83 # used by parsedate
84 84 defaultdateformats = (
85 85 '%Y-%m-%d %H:%M:%S',
86 86 '%Y-%m-%d %I:%M:%S%p',
87 87 '%Y-%m-%d %H:%M',
88 88 '%Y-%m-%d %I:%M%p',
89 89 '%Y-%m-%d',
90 90 '%m-%d',
91 91 '%m/%d',
92 92 '%m/%d/%y',
93 93 '%m/%d/%Y',
94 94 '%a %b %d %H:%M:%S %Y',
95 95 '%a %b %d %I:%M:%S%p %Y',
96 96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 97 '%b %d %H:%M:%S %Y',
98 98 '%b %d %I:%M:%S%p %Y',
99 99 '%b %d %H:%M:%S',
100 100 '%b %d %I:%M:%S%p',
101 101 '%b %d %H:%M',
102 102 '%b %d %I:%M%p',
103 103 '%b %d %Y',
104 104 '%b %d',
105 105 '%H:%M:%S',
106 106 '%I:%M:%S%p',
107 107 '%H:%M',
108 108 '%I:%M%p',
109 109 )
110 110
111 111 extendeddateformats = defaultdateformats + (
112 112 "%Y",
113 113 "%Y-%m",
114 114 "%b",
115 115 "%b %Y",
116 116 )
117 117
118 118 def cachefunc(func):
119 119 '''cache the result of function calls'''
120 120 # XXX doesn't handle keywords args
121 121 cache = {}
122 122 if func.func_code.co_argcount == 1:
123 123 # we gain a small amount of time because
124 124 # we don't need to pack/unpack the list
125 125 def f(arg):
126 126 if arg not in cache:
127 127 cache[arg] = func(arg)
128 128 return cache[arg]
129 129 else:
130 130 def f(*args):
131 131 if args not in cache:
132 132 cache[args] = func(*args)
133 133 return cache[args]
134 134
135 135 return f
136 136
137 137 def lrucachefunc(func):
138 138 '''cache most recent results of function calls'''
139 139 cache = {}
140 140 order = []
141 141 if func.func_code.co_argcount == 1:
142 142 def f(arg):
143 143 if arg not in cache:
144 144 if len(cache) > 20:
145 145 del cache[order.pop(0)]
146 146 cache[arg] = func(arg)
147 147 else:
148 148 order.remove(arg)
149 149 order.append(arg)
150 150 return cache[arg]
151 151 else:
152 152 def f(*args):
153 153 if args not in cache:
154 154 if len(cache) > 20:
155 155 del cache[order.pop(0)]
156 156 cache[args] = func(*args)
157 157 else:
158 158 order.remove(args)
159 159 order.append(args)
160 160 return cache[args]
161 161
162 162 return f
163 163
164 164 class propertycache(object):
165 165 def __init__(self, func):
166 166 self.func = func
167 167 self.name = func.__name__
168 168 def __get__(self, obj, type=None):
169 169 result = self.func(obj)
170 170 setattr(obj, self.name, result)
171 171 return result
172 172
173 173 def pipefilter(s, cmd):
174 174 '''filter string S through command CMD, returning its output'''
175 175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 177 pout, perr = p.communicate(s)
178 178 return pout
179 179
180 180 def tempfilter(s, cmd):
181 181 '''filter string S through a pair of temporary files with CMD.
182 182 CMD is used as a template to create the real command to be run,
183 183 with the strings INFILE and OUTFILE replaced by the real names of
184 184 the temporary files generated.'''
185 185 inname, outname = None, None
186 186 try:
187 187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 188 fp = os.fdopen(infd, 'wb')
189 189 fp.write(s)
190 190 fp.close()
191 191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 192 os.close(outfd)
193 193 cmd = cmd.replace('INFILE', inname)
194 194 cmd = cmd.replace('OUTFILE', outname)
195 195 code = os.system(cmd)
196 196 if sys.platform == 'OpenVMS' and code & 1:
197 197 code = 0
198 198 if code:
199 199 raise Abort(_("command '%s' failed: %s") %
200 200 (cmd, explainexit(code)))
201 201 fp = open(outname, 'rb')
202 202 r = fp.read()
203 203 fp.close()
204 204 return r
205 205 finally:
206 206 try:
207 207 if inname:
208 208 os.unlink(inname)
209 209 except OSError:
210 210 pass
211 211 try:
212 212 if outname:
213 213 os.unlink(outname)
214 214 except OSError:
215 215 pass
216 216
217 217 filtertable = {
218 218 'tempfile:': tempfilter,
219 219 'pipe:': pipefilter,
220 220 }
221 221
222 222 def filter(s, cmd):
223 223 "filter a string through a command that transforms its input to its output"
224 224 for name, fn in filtertable.iteritems():
225 225 if cmd.startswith(name):
226 226 return fn(s, cmd[len(name):].lstrip())
227 227 return pipefilter(s, cmd)
228 228
229 229 def binary(s):
230 230 """return true if a string is binary data"""
231 231 return bool(s and '\0' in s)
232 232
233 233 def increasingchunks(source, min=1024, max=65536):
234 234 '''return no less than min bytes per chunk while data remains,
235 235 doubling min after each chunk until it reaches max'''
236 236 def log2(x):
237 237 if not x:
238 238 return 0
239 239 i = 0
240 240 while x:
241 241 x >>= 1
242 242 i += 1
243 243 return i - 1
244 244
245 245 buf = []
246 246 blen = 0
247 247 for chunk in source:
248 248 buf.append(chunk)
249 249 blen += len(chunk)
250 250 if blen >= min:
251 251 if min < max:
252 252 min = min << 1
253 253 nmin = 1 << log2(blen)
254 254 if nmin > min:
255 255 min = nmin
256 256 if min > max:
257 257 min = max
258 258 yield ''.join(buf)
259 259 blen = 0
260 260 buf = []
261 261 if buf:
262 262 yield ''.join(buf)
263 263
264 264 Abort = error.Abort
265 265
266 266 def always(fn):
267 267 return True
268 268
269 269 def never(fn):
270 270 return False
271 271
272 272 def pathto(root, n1, n2):
273 273 '''return the relative path from one place to another.
274 274 root should use os.sep to separate directories
275 275 n1 should use os.sep to separate directories
276 276 n2 should use "/" to separate directories
277 277 returns an os.sep-separated path.
278 278
279 279 If n1 is a relative path, it's assumed it's
280 280 relative to root.
281 281 n2 should always be relative to root.
282 282 '''
283 283 if not n1:
284 284 return localpath(n2)
285 285 if os.path.isabs(n1):
286 286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 287 return os.path.join(root, localpath(n2))
288 288 n2 = '/'.join((pconvert(root), n2))
289 289 a, b = splitpath(n1), n2.split('/')
290 290 a.reverse()
291 291 b.reverse()
292 292 while a and b and a[-1] == b[-1]:
293 293 a.pop()
294 294 b.pop()
295 295 b.reverse()
296 296 return os.sep.join((['..'] * len(a)) + b) or '.'
297 297
298 298 _hgexecutable = None
299 299
300 300 def mainfrozen():
301 301 """return True if we are a frozen executable.
302 302
303 303 The code supports py2exe (most common, Windows only) and tools/freeze
304 304 (portable, not much used).
305 305 """
306 306 return (hasattr(sys, "frozen") or # new py2exe
307 307 hasattr(sys, "importers") or # old py2exe
308 308 imp.is_frozen("__main__")) # tools/freeze
309 309
310 310 def hgexecutable():
311 311 """return location of the 'hg' executable.
312 312
313 313 Defaults to $HG or 'hg' in the search path.
314 314 """
315 315 if _hgexecutable is None:
316 316 hg = os.environ.get('HG')
317 317 if hg:
318 318 _sethgexecutable(hg)
319 319 elif mainfrozen():
320 320 _sethgexecutable(sys.executable)
321 321 else:
322 322 exe = findexe('hg') or os.path.basename(sys.argv[0])
323 323 _sethgexecutable(exe)
324 324 return _hgexecutable
325 325
326 326 def _sethgexecutable(path):
327 327 """set location of the 'hg' executable"""
328 328 global _hgexecutable
329 329 _hgexecutable = path
330 330
331 331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
332 332 '''enhanced shell command execution.
333 333 run with environment maybe modified, maybe in different dir.
334 334
335 335 if command fails and onerr is None, return status. if ui object,
336 336 print error message and return status, else raise onerr object as
337 337 exception.
338 338
339 339 if out is specified, it is assumed to be a file-like object that has a
340 340 write() method. stdout and stderr will be redirected to out.'''
341 341 try:
342 342 sys.stdout.flush()
343 343 except Exception:
344 344 pass
345 345 def py2shell(val):
346 346 'convert python object into string that is useful to shell'
347 347 if val is None or val is False:
348 348 return '0'
349 349 if val is True:
350 350 return '1'
351 351 return str(val)
352 352 origcmd = cmd
353 353 cmd = quotecommand(cmd)
354 354 env = dict(os.environ)
355 355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
356 356 env['HG'] = hgexecutable()
357 357 if out is None or out == sys.__stdout__:
358 358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
359 359 env=env, cwd=cwd)
360 360 else:
361 361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
362 362 env=env, cwd=cwd, stdout=subprocess.PIPE,
363 363 stderr=subprocess.STDOUT)
364 364 for line in proc.stdout:
365 365 out.write(line)
366 366 proc.wait()
367 367 rc = proc.returncode
368 368 if sys.platform == 'OpenVMS' and rc & 1:
369 369 rc = 0
370 370 if rc and onerr:
371 371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 372 explainexit(rc)[0])
373 373 if errprefix:
374 374 errmsg = '%s: %s' % (errprefix, errmsg)
375 375 try:
376 376 onerr.warn(errmsg + '\n')
377 377 except AttributeError:
378 378 raise onerr(errmsg)
379 379 return rc
380 380
381 381 def checksignature(func):
382 382 '''wrap a function with code to check for calling errors'''
383 383 def check(*args, **kwargs):
384 384 try:
385 385 return func(*args, **kwargs)
386 386 except TypeError:
387 387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 388 raise error.SignatureError
389 389 raise
390 390
391 391 return check
392 392
393 393 def copyfile(src, dest):
394 394 "copy a file, preserving mode and atime/mtime"
395 395 if os.path.islink(src):
396 396 try:
397 397 os.unlink(dest)
398 398 except OSError:
399 399 pass
400 400 os.symlink(os.readlink(src), dest)
401 401 else:
402 402 try:
403 403 shutil.copyfile(src, dest)
404 404 shutil.copymode(src, dest)
405 405 except shutil.Error, inst:
406 406 raise Abort(str(inst))
407 407
408 408 def copyfiles(src, dst, hardlink=None):
409 409 """Copy a directory tree using hardlinks if possible"""
410 410
411 411 if hardlink is None:
412 412 hardlink = (os.stat(src).st_dev ==
413 413 os.stat(os.path.dirname(dst)).st_dev)
414 414
415 415 num = 0
416 416 if os.path.isdir(src):
417 417 os.mkdir(dst)
418 418 for name, kind in osutil.listdir(src):
419 419 srcname = os.path.join(src, name)
420 420 dstname = os.path.join(dst, name)
421 421 hardlink, n = copyfiles(srcname, dstname, hardlink)
422 422 num += n
423 423 else:
424 424 if hardlink:
425 425 try:
426 426 oslink(src, dst)
427 427 except (IOError, OSError):
428 428 hardlink = False
429 429 shutil.copy(src, dst)
430 430 else:
431 431 shutil.copy(src, dst)
432 432 num += 1
433 433
434 434 return hardlink, num
435 435
436 436 _winreservednames = '''con prn aux nul
437 437 com1 com2 com3 com4 com5 com6 com7 com8 com9
438 438 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
439 439 _winreservedchars = ':*?"<>|'
440 440 def checkwinfilename(path):
441 441 '''Check that the base-relative path is a valid filename on Windows.
442 442 Returns None if the path is ok, or a UI string describing the problem.
443 443
444 444 >>> checkwinfilename("just/a/normal/path")
445 445 >>> checkwinfilename("foo/bar/con.xml")
446 446 "filename contains 'con', which is reserved on Windows"
447 447 >>> checkwinfilename("foo/con.xml/bar")
448 448 "filename contains 'con', which is reserved on Windows"
449 449 >>> checkwinfilename("foo/bar/xml.con")
450 450 >>> checkwinfilename("foo/bar/AUX/bla.txt")
451 451 "filename contains 'AUX', which is reserved on Windows"
452 452 >>> checkwinfilename("foo/bar/bla:.txt")
453 453 "filename contains ':', which is reserved on Windows"
454 454 >>> checkwinfilename("foo/bar/b\07la.txt")
455 455 "filename contains '\\\\x07', which is invalid on Windows"
456 456 >>> checkwinfilename("foo/bar/bla ")
457 457 "filename ends with ' ', which is not allowed on Windows"
458 458 '''
459 459 for n in path.replace('\\', '/').split('/'):
460 460 if not n:
461 461 continue
462 462 for c in n:
463 463 if c in _winreservedchars:
464 464 return _("filename contains '%s', which is reserved "
465 465 "on Windows") % c
466 466 if ord(c) <= 31:
467 467 return _("filename contains %r, which is invalid "
468 468 "on Windows") % c
469 469 base = n.split('.')[0]
470 470 if base and base.lower() in _winreservednames:
471 471 return _("filename contains '%s', which is reserved "
472 472 "on Windows") % base
473 473 t = n[-1]
474 474 if t in '. ':
475 475 return _("filename ends with '%s', which is not allowed "
476 476 "on Windows") % t
477 477
478 def hidewindow():
479 """Hide current shell window.
480
481 Used to hide the window opened when starting asynchronous
482 child process under Windows, unneeded on other systems.
483 """
484 pass
485
486 478 if os.name == 'nt':
487 479 checkosfilename = checkwinfilename
488 480 from windows import *
489 481 else:
490 482 from posix import *
491 483
492 484 def makelock(info, pathname):
493 485 try:
494 486 return os.symlink(info, pathname)
495 487 except OSError, why:
496 488 if why.errno == errno.EEXIST:
497 489 raise
498 490 except AttributeError: # no symlink in os
499 491 pass
500 492
501 493 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
502 494 os.write(ld, info)
503 495 os.close(ld)
504 496
505 497 def readlock(pathname):
506 498 try:
507 499 return os.readlink(pathname)
508 500 except OSError, why:
509 501 if why.errno not in (errno.EINVAL, errno.ENOSYS):
510 502 raise
511 503 except AttributeError: # no symlink in os
512 504 pass
513 505 fp = posixfile(pathname)
514 506 r = fp.read()
515 507 fp.close()
516 508 return r
517 509
518 510 def fstat(fp):
519 511 '''stat file object that may not have fileno method.'''
520 512 try:
521 513 return os.fstat(fp.fileno())
522 514 except AttributeError:
523 515 return os.stat(fp.name)
524 516
525 517 # File system features
526 518
527 519 def checkcase(path):
528 520 """
529 521 Check whether the given path is on a case-sensitive filesystem
530 522
531 523 Requires a path (like /foo/.hg) ending with a foldable final
532 524 directory component.
533 525 """
534 526 s1 = os.stat(path)
535 527 d, b = os.path.split(path)
536 528 p2 = os.path.join(d, b.upper())
537 529 if path == p2:
538 530 p2 = os.path.join(d, b.lower())
539 531 try:
540 532 s2 = os.stat(p2)
541 533 if s2 == s1:
542 534 return False
543 535 return True
544 536 except OSError:
545 537 return True
546 538
547 539 _fspathcache = {}
548 540 def fspath(name, root):
549 541 '''Get name in the case stored in the filesystem
550 542
551 543 The name is either relative to root, or it is an absolute path starting
552 544 with root. Note that this function is unnecessary, and should not be
553 545 called, for case-sensitive filesystems (simply because it's expensive).
554 546 '''
555 547 # If name is absolute, make it relative
556 548 if name.lower().startswith(root.lower()):
557 549 l = len(root)
558 550 if name[l] == os.sep or name[l] == os.altsep:
559 551 l = l + 1
560 552 name = name[l:]
561 553
562 554 if not os.path.lexists(os.path.join(root, name)):
563 555 return None
564 556
565 557 seps = os.sep
566 558 if os.altsep:
567 559 seps = seps + os.altsep
568 560 # Protect backslashes. This gets silly very quickly.
569 561 seps.replace('\\','\\\\')
570 562 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
571 563 dir = os.path.normcase(os.path.normpath(root))
572 564 result = []
573 565 for part, sep in pattern.findall(name):
574 566 if sep:
575 567 result.append(sep)
576 568 continue
577 569
578 570 if dir not in _fspathcache:
579 571 _fspathcache[dir] = os.listdir(dir)
580 572 contents = _fspathcache[dir]
581 573
582 574 lpart = part.lower()
583 575 lenp = len(part)
584 576 for n in contents:
585 577 if lenp == len(n) and n.lower() == lpart:
586 578 result.append(n)
587 579 break
588 580 else:
589 581 # Cannot happen, as the file exists!
590 582 result.append(part)
591 583 dir = os.path.join(dir, lpart)
592 584
593 585 return ''.join(result)
594 586
595 587 def checknlink(testfile):
596 588 '''check whether hardlink count reporting works properly'''
597 589
598 590 # testfile may be open, so we need a separate file for checking to
599 591 # work around issue2543 (or testfile may get lost on Samba shares)
600 592 f1 = testfile + ".hgtmp1"
601 593 if os.path.lexists(f1):
602 594 return False
603 595 try:
604 596 posixfile(f1, 'w').close()
605 597 except IOError:
606 598 return False
607 599
608 600 f2 = testfile + ".hgtmp2"
609 601 fd = None
610 602 try:
611 603 try:
612 604 oslink(f1, f2)
613 605 except OSError:
614 606 return False
615 607
616 608 # nlinks() may behave differently for files on Windows shares if
617 609 # the file is open.
618 610 fd = posixfile(f2)
619 611 return nlinks(f2) > 1
620 612 finally:
621 613 if fd is not None:
622 614 fd.close()
623 615 for f in (f1, f2):
624 616 try:
625 617 os.unlink(f)
626 618 except OSError:
627 619 pass
628 620
629 621 return False
630 622
631 623 def endswithsep(path):
632 624 '''Check path ends with os.sep or os.altsep.'''
633 625 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
634 626
635 627 def splitpath(path):
636 628 '''Split path by os.sep.
637 629 Note that this function does not use os.altsep because this is
638 630 an alternative of simple "xxx.split(os.sep)".
639 631 It is recommended to use os.path.normpath() before using this
640 632 function if need.'''
641 633 return path.split(os.sep)
642 634
643 635 def gui():
644 636 '''Are we running in a GUI?'''
645 637 if sys.platform == 'darwin':
646 638 if 'SSH_CONNECTION' in os.environ:
647 639 # handle SSH access to a box where the user is logged in
648 640 return False
649 641 elif getattr(osutil, 'isgui', None):
650 642 # check if a CoreGraphics session is available
651 643 return osutil.isgui()
652 644 else:
653 645 # pure build; use a safe default
654 646 return True
655 647 else:
656 648 return os.name == "nt" or os.environ.get("DISPLAY")
657 649
658 650 def mktempcopy(name, emptyok=False, createmode=None):
659 651 """Create a temporary file with the same contents from name
660 652
661 653 The permission bits are copied from the original file.
662 654
663 655 If the temporary file is going to be truncated immediately, you
664 656 can use emptyok=True as an optimization.
665 657
666 658 Returns the name of the temporary file.
667 659 """
668 660 d, fn = os.path.split(name)
669 661 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
670 662 os.close(fd)
671 663 # Temporary files are created with mode 0600, which is usually not
672 664 # what we want. If the original file already exists, just copy
673 665 # its mode. Otherwise, manually obey umask.
674 666 try:
675 667 st_mode = os.lstat(name).st_mode & 0777
676 668 except OSError, inst:
677 669 if inst.errno != errno.ENOENT:
678 670 raise
679 671 st_mode = createmode
680 672 if st_mode is None:
681 673 st_mode = ~umask
682 674 st_mode &= 0666
683 675 os.chmod(temp, st_mode)
684 676 if emptyok:
685 677 return temp
686 678 try:
687 679 try:
688 680 ifp = posixfile(name, "rb")
689 681 except IOError, inst:
690 682 if inst.errno == errno.ENOENT:
691 683 return temp
692 684 if not getattr(inst, 'filename', None):
693 685 inst.filename = name
694 686 raise
695 687 ofp = posixfile(temp, "wb")
696 688 for chunk in filechunkiter(ifp):
697 689 ofp.write(chunk)
698 690 ifp.close()
699 691 ofp.close()
700 692 except:
701 693 try: os.unlink(temp)
702 694 except: pass
703 695 raise
704 696 return temp
705 697
706 698 class atomictempfile(object):
707 699 '''writeable file object that atomically updates a file
708 700
709 701 All writes will go to a temporary copy of the original file. Call
710 702 rename() when you are done writing, and atomictempfile will rename
711 703 the temporary copy to the original name, making the changes visible.
712 704
713 705 Unlike other file-like objects, close() discards your writes by
714 706 simply deleting the temporary file.
715 707 '''
716 708 def __init__(self, name, mode='w+b', createmode=None):
717 709 self.__name = name # permanent name
718 710 self._tempname = mktempcopy(name, emptyok=('w' in mode),
719 711 createmode=createmode)
720 712 self._fp = posixfile(self._tempname, mode)
721 713
722 714 # delegated methods
723 715 self.write = self._fp.write
724 716 self.fileno = self._fp.fileno
725 717
726 718 def rename(self):
727 719 if not self._fp.closed:
728 720 self._fp.close()
729 721 rename(self._tempname, localpath(self.__name))
730 722
731 723 def close(self):
732 724 if not self._fp.closed:
733 725 try:
734 726 os.unlink(self._tempname)
735 727 except OSError:
736 728 pass
737 729 self._fp.close()
738 730
739 731 def __del__(self):
740 732 if hasattr(self, '_fp'): # constructor actually did something
741 733 self.close()
742 734
743 735 def makedirs(name, mode=None):
744 736 """recursive directory creation with parent mode inheritance"""
745 737 parent = os.path.abspath(os.path.dirname(name))
746 738 try:
747 739 os.mkdir(name)
748 740 if mode is not None:
749 741 os.chmod(name, mode)
750 742 return
751 743 except OSError, err:
752 744 if err.errno == errno.EEXIST:
753 745 return
754 746 if not name or parent == name or err.errno != errno.ENOENT:
755 747 raise
756 748 makedirs(parent, mode)
757 749 makedirs(name, mode)
758 750
759 751 def readfile(path):
760 752 fp = open(path, 'rb')
761 753 try:
762 754 return fp.read()
763 755 finally:
764 756 fp.close()
765 757
766 758 def writefile(path, text):
767 759 fp = open(path, 'wb')
768 760 try:
769 761 fp.write(text)
770 762 finally:
771 763 fp.close()
772 764
773 765 def appendfile(path, text):
774 766 fp = open(path, 'ab')
775 767 try:
776 768 fp.write(text)
777 769 finally:
778 770 fp.close()
779 771
780 772 class chunkbuffer(object):
781 773 """Allow arbitrary sized chunks of data to be efficiently read from an
782 774 iterator over chunks of arbitrary size."""
783 775
784 776 def __init__(self, in_iter):
785 777 """in_iter is the iterator that's iterating over the input chunks.
786 778 targetsize is how big a buffer to try to maintain."""
787 779 def splitbig(chunks):
788 780 for chunk in chunks:
789 781 if len(chunk) > 2**20:
790 782 pos = 0
791 783 while pos < len(chunk):
792 784 end = pos + 2 ** 18
793 785 yield chunk[pos:end]
794 786 pos = end
795 787 else:
796 788 yield chunk
797 789 self.iter = splitbig(in_iter)
798 790 self._queue = []
799 791
800 792 def read(self, l):
801 793 """Read L bytes of data from the iterator of chunks of data.
802 794 Returns less than L bytes if the iterator runs dry."""
803 795 left = l
804 796 buf = ''
805 797 queue = self._queue
806 798 while left > 0:
807 799 # refill the queue
808 800 if not queue:
809 801 target = 2**18
810 802 for chunk in self.iter:
811 803 queue.append(chunk)
812 804 target -= len(chunk)
813 805 if target <= 0:
814 806 break
815 807 if not queue:
816 808 break
817 809
818 810 chunk = queue.pop(0)
819 811 left -= len(chunk)
820 812 if left < 0:
821 813 queue.insert(0, chunk[left:])
822 814 buf += chunk[:left]
823 815 else:
824 816 buf += chunk
825 817
826 818 return buf
827 819
828 820 def filechunkiter(f, size=65536, limit=None):
829 821 """Create a generator that produces the data in the file size
830 822 (default 65536) bytes at a time, up to optional limit (default is
831 823 to read all data). Chunks may be less than size bytes if the
832 824 chunk is the last chunk in the file, or the file is a socket or
833 825 some other type of file that sometimes reads less data than is
834 826 requested."""
835 827 assert size >= 0
836 828 assert limit is None or limit >= 0
837 829 while True:
838 830 if limit is None:
839 831 nbytes = size
840 832 else:
841 833 nbytes = min(limit, size)
842 834 s = nbytes and f.read(nbytes)
843 835 if not s:
844 836 break
845 837 if limit:
846 838 limit -= len(s)
847 839 yield s
848 840
849 841 def makedate():
850 842 lt = time.localtime()
851 843 if lt[8] == 1 and time.daylight:
852 844 tz = time.altzone
853 845 else:
854 846 tz = time.timezone
855 847 t = time.mktime(lt)
856 848 if t < 0:
857 849 hint = _("check your clock")
858 850 raise Abort(_("negative timestamp: %d") % t, hint=hint)
859 851 return t, tz
860 852
861 853 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
862 854 """represent a (unixtime, offset) tuple as a localized time.
863 855 unixtime is seconds since the epoch, and offset is the time zone's
864 856 number of seconds away from UTC. if timezone is false, do not
865 857 append time zone to string."""
866 858 t, tz = date or makedate()
867 859 if t < 0:
868 860 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
869 861 tz = 0
870 862 if "%1" in format or "%2" in format:
871 863 sign = (tz > 0) and "-" or "+"
872 864 minutes = abs(tz) // 60
873 865 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
874 866 format = format.replace("%2", "%02d" % (minutes % 60))
875 867 s = time.strftime(format, time.gmtime(float(t) - tz))
876 868 return s
877 869
878 870 def shortdate(date=None):
879 871 """turn (timestamp, tzoff) tuple into iso 8631 date."""
880 872 return datestr(date, format='%Y-%m-%d')
881 873
882 874 def strdate(string, format, defaults=[]):
883 875 """parse a localized time string and return a (unixtime, offset) tuple.
884 876 if the string cannot be parsed, ValueError is raised."""
885 877 def timezone(string):
886 878 tz = string.split()[-1]
887 879 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
888 880 sign = (tz[0] == "+") and 1 or -1
889 881 hours = int(tz[1:3])
890 882 minutes = int(tz[3:5])
891 883 return -sign * (hours * 60 + minutes) * 60
892 884 if tz == "GMT" or tz == "UTC":
893 885 return 0
894 886 return None
895 887
896 888 # NOTE: unixtime = localunixtime + offset
897 889 offset, date = timezone(string), string
898 890 if offset is not None:
899 891 date = " ".join(string.split()[:-1])
900 892
901 893 # add missing elements from defaults
902 894 usenow = False # default to using biased defaults
903 895 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
904 896 found = [True for p in part if ("%"+p) in format]
905 897 if not found:
906 898 date += "@" + defaults[part][usenow]
907 899 format += "@%" + part[0]
908 900 else:
909 901 # We've found a specific time element, less specific time
910 902 # elements are relative to today
911 903 usenow = True
912 904
913 905 timetuple = time.strptime(date, format)
914 906 localunixtime = int(calendar.timegm(timetuple))
915 907 if offset is None:
916 908 # local timezone
917 909 unixtime = int(time.mktime(timetuple))
918 910 offset = unixtime - localunixtime
919 911 else:
920 912 unixtime = localunixtime + offset
921 913 return unixtime, offset
922 914
923 915 def parsedate(date, formats=None, bias={}):
924 916 """parse a localized date/time and return a (unixtime, offset) tuple.
925 917
926 918 The date may be a "unixtime offset" string or in one of the specified
927 919 formats. If the date already is a (unixtime, offset) tuple, it is returned.
928 920 """
929 921 if not date:
930 922 return 0, 0
931 923 if isinstance(date, tuple) and len(date) == 2:
932 924 return date
933 925 if not formats:
934 926 formats = defaultdateformats
935 927 date = date.strip()
936 928 try:
937 929 when, offset = map(int, date.split(' '))
938 930 except ValueError:
939 931 # fill out defaults
940 932 now = makedate()
941 933 defaults = {}
942 934 for part in ("d", "mb", "yY", "HI", "M", "S"):
943 935 # this piece is for rounding the specific end of unknowns
944 936 b = bias.get(part)
945 937 if b is None:
946 938 if part[0] in "HMS":
947 939 b = "00"
948 940 else:
949 941 b = "0"
950 942
951 943 # this piece is for matching the generic end to today's date
952 944 n = datestr(now, "%" + part[0])
953 945
954 946 defaults[part] = (b, n)
955 947
956 948 for format in formats:
957 949 try:
958 950 when, offset = strdate(date, format, defaults)
959 951 except (ValueError, OverflowError):
960 952 pass
961 953 else:
962 954 break
963 955 else:
964 956 raise Abort(_('invalid date: %r') % date)
965 957 # validate explicit (probably user-specified) date and
966 958 # time zone offset. values must fit in signed 32 bits for
967 959 # current 32-bit linux runtimes. timezones go from UTC-12
968 960 # to UTC+14
969 961 if abs(when) > 0x7fffffff:
970 962 raise Abort(_('date exceeds 32 bits: %d') % when)
971 963 if when < 0:
972 964 raise Abort(_('negative date value: %d') % when)
973 965 if offset < -50400 or offset > 43200:
974 966 raise Abort(_('impossible time zone offset: %d') % offset)
975 967 return when, offset
976 968
977 969 def matchdate(date):
978 970 """Return a function that matches a given date match specifier
979 971
980 972 Formats include:
981 973
982 974 '{date}' match a given date to the accuracy provided
983 975
984 976 '<{date}' on or before a given date
985 977
986 978 '>{date}' on or after a given date
987 979
988 980 >>> p1 = parsedate("10:29:59")
989 981 >>> p2 = parsedate("10:30:00")
990 982 >>> p3 = parsedate("10:30:59")
991 983 >>> p4 = parsedate("10:31:00")
992 984 >>> p5 = parsedate("Sep 15 10:30:00 1999")
993 985 >>> f = matchdate("10:30")
994 986 >>> f(p1[0])
995 987 False
996 988 >>> f(p2[0])
997 989 True
998 990 >>> f(p3[0])
999 991 True
1000 992 >>> f(p4[0])
1001 993 False
1002 994 >>> f(p5[0])
1003 995 False
1004 996 """
1005 997
1006 998 def lower(date):
1007 999 d = dict(mb="1", d="1")
1008 1000 return parsedate(date, extendeddateformats, d)[0]
1009 1001
1010 1002 def upper(date):
1011 1003 d = dict(mb="12", HI="23", M="59", S="59")
1012 1004 for days in ("31", "30", "29"):
1013 1005 try:
1014 1006 d["d"] = days
1015 1007 return parsedate(date, extendeddateformats, d)[0]
1016 1008 except:
1017 1009 pass
1018 1010 d["d"] = "28"
1019 1011 return parsedate(date, extendeddateformats, d)[0]
1020 1012
1021 1013 date = date.strip()
1022 1014
1023 1015 if not date:
1024 1016 raise Abort(_("dates cannot consist entirely of whitespace"))
1025 1017 elif date[0] == "<":
1026 1018 if not date[1:]:
1027 1019 raise Abort(_("invalid day spec, use '<DATE'"))
1028 1020 when = upper(date[1:])
1029 1021 return lambda x: x <= when
1030 1022 elif date[0] == ">":
1031 1023 if not date[1:]:
1032 1024 raise Abort(_("invalid day spec, use '>DATE'"))
1033 1025 when = lower(date[1:])
1034 1026 return lambda x: x >= when
1035 1027 elif date[0] == "-":
1036 1028 try:
1037 1029 days = int(date[1:])
1038 1030 except ValueError:
1039 1031 raise Abort(_("invalid day spec: %s") % date[1:])
1040 1032 if days < 0:
1041 1033 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1042 1034 % date[1:])
1043 1035 when = makedate()[0] - days * 3600 * 24
1044 1036 return lambda x: x >= when
1045 1037 elif " to " in date:
1046 1038 a, b = date.split(" to ")
1047 1039 start, stop = lower(a), upper(b)
1048 1040 return lambda x: x >= start and x <= stop
1049 1041 else:
1050 1042 start, stop = lower(date), upper(date)
1051 1043 return lambda x: x >= start and x <= stop
1052 1044
1053 1045 def shortuser(user):
1054 1046 """Return a short representation of a user name or email address."""
1055 1047 f = user.find('@')
1056 1048 if f >= 0:
1057 1049 user = user[:f]
1058 1050 f = user.find('<')
1059 1051 if f >= 0:
1060 1052 user = user[f + 1:]
1061 1053 f = user.find(' ')
1062 1054 if f >= 0:
1063 1055 user = user[:f]
1064 1056 f = user.find('.')
1065 1057 if f >= 0:
1066 1058 user = user[:f]
1067 1059 return user
1068 1060
1069 1061 def email(author):
1070 1062 '''get email of author.'''
1071 1063 r = author.find('>')
1072 1064 if r == -1:
1073 1065 r = None
1074 1066 return author[author.find('<') + 1:r]
1075 1067
1076 1068 def _ellipsis(text, maxlength):
1077 1069 if len(text) <= maxlength:
1078 1070 return text, False
1079 1071 else:
1080 1072 return "%s..." % (text[:maxlength - 3]), True
1081 1073
1082 1074 def ellipsis(text, maxlength=400):
1083 1075 """Trim string to at most maxlength (default: 400) characters."""
1084 1076 try:
1085 1077 # use unicode not to split at intermediate multi-byte sequence
1086 1078 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1087 1079 maxlength)
1088 1080 if not truncated:
1089 1081 return text
1090 1082 return utext.encode(encoding.encoding)
1091 1083 except (UnicodeDecodeError, UnicodeEncodeError):
1092 1084 return _ellipsis(text, maxlength)[0]
1093 1085
1094 1086 def bytecount(nbytes):
1095 1087 '''return byte count formatted as readable string, with units'''
1096 1088
1097 1089 units = (
1098 1090 (100, 1 << 30, _('%.0f GB')),
1099 1091 (10, 1 << 30, _('%.1f GB')),
1100 1092 (1, 1 << 30, _('%.2f GB')),
1101 1093 (100, 1 << 20, _('%.0f MB')),
1102 1094 (10, 1 << 20, _('%.1f MB')),
1103 1095 (1, 1 << 20, _('%.2f MB')),
1104 1096 (100, 1 << 10, _('%.0f KB')),
1105 1097 (10, 1 << 10, _('%.1f KB')),
1106 1098 (1, 1 << 10, _('%.2f KB')),
1107 1099 (1, 1, _('%.0f bytes')),
1108 1100 )
1109 1101
1110 1102 for multiplier, divisor, format in units:
1111 1103 if nbytes >= divisor * multiplier:
1112 1104 return format % (nbytes / float(divisor))
1113 1105 return units[-1][2] % nbytes
1114 1106
1115 1107 def uirepr(s):
1116 1108 # Avoid double backslash in Windows path repr()
1117 1109 return repr(s).replace('\\\\', '\\')
1118 1110
1119 1111 # delay import of textwrap
1120 1112 def MBTextWrapper(**kwargs):
1121 1113 class tw(textwrap.TextWrapper):
1122 1114 """
1123 1115 Extend TextWrapper for double-width characters.
1124 1116
1125 1117 Some Asian characters use two terminal columns instead of one.
1126 1118 A good example of this behavior can be seen with u'\u65e5\u672c',
1127 1119 the two Japanese characters for "Japan":
1128 1120 len() returns 2, but when printed to a terminal, they eat 4 columns.
1129 1121
1130 1122 (Note that this has nothing to do whatsoever with unicode
1131 1123 representation, or encoding of the underlying string)
1132 1124 """
1133 1125 def __init__(self, **kwargs):
1134 1126 textwrap.TextWrapper.__init__(self, **kwargs)
1135 1127
1136 1128 def _cutdown(self, str, space_left):
1137 1129 l = 0
1138 1130 ucstr = unicode(str, encoding.encoding)
1139 1131 colwidth = unicodedata.east_asian_width
1140 1132 for i in xrange(len(ucstr)):
1141 1133 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1142 1134 if space_left < l:
1143 1135 return (ucstr[:i].encode(encoding.encoding),
1144 1136 ucstr[i:].encode(encoding.encoding))
1145 1137 return str, ''
1146 1138
1147 1139 # overriding of base class
1148 1140 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1149 1141 space_left = max(width - cur_len, 1)
1150 1142
1151 1143 if self.break_long_words:
1152 1144 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1153 1145 cur_line.append(cut)
1154 1146 reversed_chunks[-1] = res
1155 1147 elif not cur_line:
1156 1148 cur_line.append(reversed_chunks.pop())
1157 1149
1158 1150 global MBTextWrapper
1159 1151 MBTextWrapper = tw
1160 1152 return tw(**kwargs)
1161 1153
1162 1154 def wrap(line, width, initindent='', hangindent=''):
1163 1155 maxindent = max(len(hangindent), len(initindent))
1164 1156 if width <= maxindent:
1165 1157 # adjust for weird terminal size
1166 1158 width = max(78, maxindent + 1)
1167 1159 wrapper = MBTextWrapper(width=width,
1168 1160 initial_indent=initindent,
1169 1161 subsequent_indent=hangindent)
1170 1162 return wrapper.fill(line)
1171 1163
1172 1164 def iterlines(iterator):
1173 1165 for chunk in iterator:
1174 1166 for line in chunk.splitlines():
1175 1167 yield line
1176 1168
1177 1169 def expandpath(path):
1178 1170 return os.path.expanduser(os.path.expandvars(path))
1179 1171
1180 1172 def hgcmd():
1181 1173 """Return the command used to execute current hg
1182 1174
1183 1175 This is different from hgexecutable() because on Windows we want
1184 1176 to avoid things opening new shell windows like batch files, so we
1185 1177 get either the python call or current executable.
1186 1178 """
1187 1179 if mainfrozen():
1188 1180 return [sys.executable]
1189 1181 return gethgcmd()
1190 1182
1191 1183 def rundetached(args, condfn):
1192 1184 """Execute the argument list in a detached process.
1193 1185
1194 1186 condfn is a callable which is called repeatedly and should return
1195 1187 True once the child process is known to have started successfully.
1196 1188 At this point, the child process PID is returned. If the child
1197 1189 process fails to start or finishes before condfn() evaluates to
1198 1190 True, return -1.
1199 1191 """
1200 1192 # Windows case is easier because the child process is either
1201 1193 # successfully starting and validating the condition or exiting
1202 1194 # on failure. We just poll on its PID. On Unix, if the child
1203 1195 # process fails to start, it will be left in a zombie state until
1204 1196 # the parent wait on it, which we cannot do since we expect a long
1205 1197 # running process on success. Instead we listen for SIGCHLD telling
1206 1198 # us our child process terminated.
1207 1199 terminated = set()
1208 1200 def handler(signum, frame):
1209 1201 terminated.add(os.wait())
1210 1202 prevhandler = None
1211 1203 if hasattr(signal, 'SIGCHLD'):
1212 1204 prevhandler = signal.signal(signal.SIGCHLD, handler)
1213 1205 try:
1214 1206 pid = spawndetached(args)
1215 1207 while not condfn():
1216 1208 if ((pid in terminated or not testpid(pid))
1217 1209 and not condfn()):
1218 1210 return -1
1219 1211 time.sleep(0.1)
1220 1212 return pid
1221 1213 finally:
1222 1214 if prevhandler is not None:
1223 1215 signal.signal(signal.SIGCHLD, prevhandler)
1224 1216
1225 1217 try:
1226 1218 any, all = any, all
1227 1219 except NameError:
1228 1220 def any(iterable):
1229 1221 for i in iterable:
1230 1222 if i:
1231 1223 return True
1232 1224 return False
1233 1225
1234 1226 def all(iterable):
1235 1227 for i in iterable:
1236 1228 if not i:
1237 1229 return False
1238 1230 return True
1239 1231
1240 1232 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1241 1233 """Return the result of interpolating items in the mapping into string s.
1242 1234
1243 1235 prefix is a single character string, or a two character string with
1244 1236 a backslash as the first character if the prefix needs to be escaped in
1245 1237 a regular expression.
1246 1238
1247 1239 fn is an optional function that will be applied to the replacement text
1248 1240 just before replacement.
1249 1241
1250 1242 escape_prefix is an optional flag that allows using doubled prefix for
1251 1243 its escaping.
1252 1244 """
1253 1245 fn = fn or (lambda s: s)
1254 1246 patterns = '|'.join(mapping.keys())
1255 1247 if escape_prefix:
1256 1248 patterns += '|' + prefix
1257 1249 if len(prefix) > 1:
1258 1250 prefix_char = prefix[1:]
1259 1251 else:
1260 1252 prefix_char = prefix
1261 1253 mapping[prefix_char] = prefix_char
1262 1254 r = re.compile(r'%s(%s)' % (prefix, patterns))
1263 1255 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1264 1256
1265 1257 def getport(port):
1266 1258 """Return the port for a given network service.
1267 1259
1268 1260 If port is an integer, it's returned as is. If it's a string, it's
1269 1261 looked up using socket.getservbyname(). If there's no matching
1270 1262 service, util.Abort is raised.
1271 1263 """
1272 1264 try:
1273 1265 return int(port)
1274 1266 except ValueError:
1275 1267 pass
1276 1268
1277 1269 try:
1278 1270 return socket.getservbyname(port)
1279 1271 except socket.error:
1280 1272 raise Abort(_("no port number associated with service '%s'") % port)
1281 1273
1282 1274 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1283 1275 '0': False, 'no': False, 'false': False, 'off': False,
1284 1276 'never': False}
1285 1277
1286 1278 def parsebool(s):
1287 1279 """Parse s into a boolean.
1288 1280
1289 1281 If s is not a valid boolean, returns None.
1290 1282 """
1291 1283 return _booleans.get(s.lower(), None)
1292 1284
1293 1285 _hexdig = '0123456789ABCDEFabcdef'
1294 1286 _hextochr = dict((a + b, chr(int(a + b, 16)))
1295 1287 for a in _hexdig for b in _hexdig)
1296 1288
1297 1289 def _urlunquote(s):
1298 1290 """unquote('abc%20def') -> 'abc def'."""
1299 1291 res = s.split('%')
1300 1292 # fastpath
1301 1293 if len(res) == 1:
1302 1294 return s
1303 1295 s = res[0]
1304 1296 for item in res[1:]:
1305 1297 try:
1306 1298 s += _hextochr[item[:2]] + item[2:]
1307 1299 except KeyError:
1308 1300 s += '%' + item
1309 1301 except UnicodeDecodeError:
1310 1302 s += unichr(int(item[:2], 16)) + item[2:]
1311 1303 return s
1312 1304
1313 1305 class url(object):
1314 1306 r"""Reliable URL parser.
1315 1307
1316 1308 This parses URLs and provides attributes for the following
1317 1309 components:
1318 1310
1319 1311 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1320 1312
1321 1313 Missing components are set to None. The only exception is
1322 1314 fragment, which is set to '' if present but empty.
1323 1315
1324 1316 If parsefragment is False, fragment is included in query. If
1325 1317 parsequery is False, query is included in path. If both are
1326 1318 False, both fragment and query are included in path.
1327 1319
1328 1320 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1329 1321
1330 1322 Note that for backward compatibility reasons, bundle URLs do not
1331 1323 take host names. That means 'bundle://../' has a path of '../'.
1332 1324
1333 1325 Examples:
1334 1326
1335 1327 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1336 1328 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1337 1329 >>> url('ssh://[::1]:2200//home/joe/repo')
1338 1330 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1339 1331 >>> url('file:///home/joe/repo')
1340 1332 <url scheme: 'file', path: '/home/joe/repo'>
1341 1333 >>> url('bundle:foo')
1342 1334 <url scheme: 'bundle', path: 'foo'>
1343 1335 >>> url('bundle://../foo')
1344 1336 <url scheme: 'bundle', path: '../foo'>
1345 1337 >>> url(r'c:\foo\bar')
1346 1338 <url path: 'c:\\foo\\bar'>
1347 1339 >>> url(r'\\blah\blah\blah')
1348 1340 <url path: '\\\\blah\\blah\\blah'>
1349 1341
1350 1342 Authentication credentials:
1351 1343
1352 1344 >>> url('ssh://joe:xyz@x/repo')
1353 1345 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1354 1346 >>> url('ssh://joe@x/repo')
1355 1347 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1356 1348
1357 1349 Query strings and fragments:
1358 1350
1359 1351 >>> url('http://host/a?b#c')
1360 1352 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1361 1353 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1362 1354 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1363 1355 """
1364 1356
1365 1357 _safechars = "!~*'()+"
1366 1358 _safepchars = "/!~*'()+"
1367 1359 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1368 1360
1369 1361 def __init__(self, path, parsequery=True, parsefragment=True):
1370 1362 # We slowly chomp away at path until we have only the path left
1371 1363 self.scheme = self.user = self.passwd = self.host = None
1372 1364 self.port = self.path = self.query = self.fragment = None
1373 1365 self._localpath = True
1374 1366 self._hostport = ''
1375 1367 self._origpath = path
1376 1368
1377 1369 # special case for Windows drive letters and UNC paths
1378 1370 if hasdriveletter(path) or path.startswith(r'\\'):
1379 1371 self.path = path
1380 1372 return
1381 1373
1382 1374 # For compatibility reasons, we can't handle bundle paths as
1383 1375 # normal URLS
1384 1376 if path.startswith('bundle:'):
1385 1377 self.scheme = 'bundle'
1386 1378 path = path[7:]
1387 1379 if path.startswith('//'):
1388 1380 path = path[2:]
1389 1381 self.path = path
1390 1382 return
1391 1383
1392 1384 if self._matchscheme(path):
1393 1385 parts = path.split(':', 1)
1394 1386 if parts[0]:
1395 1387 self.scheme, path = parts
1396 1388 self._localpath = False
1397 1389
1398 1390 if not path:
1399 1391 path = None
1400 1392 if self._localpath:
1401 1393 self.path = ''
1402 1394 return
1403 1395 else:
1404 1396 if parsefragment and '#' in path:
1405 1397 path, self.fragment = path.split('#', 1)
1406 1398 if not path:
1407 1399 path = None
1408 1400 if self._localpath:
1409 1401 self.path = path
1410 1402 return
1411 1403
1412 1404 if parsequery and '?' in path:
1413 1405 path, self.query = path.split('?', 1)
1414 1406 if not path:
1415 1407 path = None
1416 1408 if not self.query:
1417 1409 self.query = None
1418 1410
1419 1411 # // is required to specify a host/authority
1420 1412 if path and path.startswith('//'):
1421 1413 parts = path[2:].split('/', 1)
1422 1414 if len(parts) > 1:
1423 1415 self.host, path = parts
1424 1416 path = path
1425 1417 else:
1426 1418 self.host = parts[0]
1427 1419 path = None
1428 1420 if not self.host:
1429 1421 self.host = None
1430 1422 if path:
1431 1423 path = '/' + path
1432 1424
1433 1425 if self.host and '@' in self.host:
1434 1426 self.user, self.host = self.host.rsplit('@', 1)
1435 1427 if ':' in self.user:
1436 1428 self.user, self.passwd = self.user.split(':', 1)
1437 1429 if not self.host:
1438 1430 self.host = None
1439 1431
1440 1432 # Don't split on colons in IPv6 addresses without ports
1441 1433 if (self.host and ':' in self.host and
1442 1434 not (self.host.startswith('[') and self.host.endswith(']'))):
1443 1435 self._hostport = self.host
1444 1436 self.host, self.port = self.host.rsplit(':', 1)
1445 1437 if not self.host:
1446 1438 self.host = None
1447 1439
1448 1440 if (self.host and self.scheme == 'file' and
1449 1441 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1450 1442 raise Abort(_('file:// URLs can only refer to localhost'))
1451 1443
1452 1444 self.path = path
1453 1445
1454 1446 for a in ('user', 'passwd', 'host', 'port',
1455 1447 'path', 'query', 'fragment'):
1456 1448 v = getattr(self, a)
1457 1449 if v is not None:
1458 1450 setattr(self, a, _urlunquote(v))
1459 1451
1460 1452 def __repr__(self):
1461 1453 attrs = []
1462 1454 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1463 1455 'query', 'fragment'):
1464 1456 v = getattr(self, a)
1465 1457 if v is not None:
1466 1458 attrs.append('%s: %r' % (a, v))
1467 1459 return '<url %s>' % ', '.join(attrs)
1468 1460
1469 1461 def __str__(self):
1470 1462 r"""Join the URL's components back into a URL string.
1471 1463
1472 1464 Examples:
1473 1465
1474 1466 >>> str(url('http://user:pw@host:80/?foo#bar'))
1475 1467 'http://user:pw@host:80/?foo#bar'
1476 1468 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1477 1469 'ssh://user:pw@[::1]:2200//home/joe#'
1478 1470 >>> str(url('http://localhost:80//'))
1479 1471 'http://localhost:80//'
1480 1472 >>> str(url('http://localhost:80/'))
1481 1473 'http://localhost:80/'
1482 1474 >>> str(url('http://localhost:80'))
1483 1475 'http://localhost:80/'
1484 1476 >>> str(url('bundle:foo'))
1485 1477 'bundle:foo'
1486 1478 >>> str(url('bundle://../foo'))
1487 1479 'bundle:../foo'
1488 1480 >>> str(url('path'))
1489 1481 'path'
1490 1482 >>> str(url('file:///tmp/foo/bar'))
1491 1483 'file:///tmp/foo/bar'
1492 1484 >>> print url(r'bundle:foo\bar')
1493 1485 bundle:foo\bar
1494 1486 """
1495 1487 if self._localpath:
1496 1488 s = self.path
1497 1489 if self.scheme == 'bundle':
1498 1490 s = 'bundle:' + s
1499 1491 if self.fragment:
1500 1492 s += '#' + self.fragment
1501 1493 return s
1502 1494
1503 1495 s = self.scheme + ':'
1504 1496 if self.user or self.passwd or self.host:
1505 1497 s += '//'
1506 1498 elif self.scheme and (not self.path or self.path.startswith('/')):
1507 1499 s += '//'
1508 1500 if self.user:
1509 1501 s += urllib.quote(self.user, safe=self._safechars)
1510 1502 if self.passwd:
1511 1503 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1512 1504 if self.user or self.passwd:
1513 1505 s += '@'
1514 1506 if self.host:
1515 1507 if not (self.host.startswith('[') and self.host.endswith(']')):
1516 1508 s += urllib.quote(self.host)
1517 1509 else:
1518 1510 s += self.host
1519 1511 if self.port:
1520 1512 s += ':' + urllib.quote(self.port)
1521 1513 if self.host:
1522 1514 s += '/'
1523 1515 if self.path:
1524 1516 s += urllib.quote(self.path, safe=self._safepchars)
1525 1517 if self.query:
1526 1518 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1527 1519 if self.fragment is not None:
1528 1520 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1529 1521 return s
1530 1522
1531 1523 def authinfo(self):
1532 1524 user, passwd = self.user, self.passwd
1533 1525 try:
1534 1526 self.user, self.passwd = None, None
1535 1527 s = str(self)
1536 1528 finally:
1537 1529 self.user, self.passwd = user, passwd
1538 1530 if not self.user:
1539 1531 return (s, None)
1540 1532 return (s, (None, (str(self), self.host),
1541 1533 self.user, self.passwd or ''))
1542 1534
1543 1535 def isabs(self):
1544 1536 if self.scheme and self.scheme != 'file':
1545 1537 return True # remote URL
1546 1538 if hasdriveletter(self.path):
1547 1539 return True # absolute for our purposes - can't be joined()
1548 1540 if self.path.startswith(r'\\'):
1549 1541 return True # Windows UNC path
1550 1542 if self.path.startswith('/'):
1551 1543 return True # POSIX-style
1552 1544 return False
1553 1545
1554 1546 def localpath(self):
1555 1547 if self.scheme == 'file' or self.scheme == 'bundle':
1556 1548 path = self.path or '/'
1557 1549 # For Windows, we need to promote hosts containing drive
1558 1550 # letters to paths with drive letters.
1559 1551 if hasdriveletter(self._hostport):
1560 1552 path = self._hostport + '/' + self.path
1561 1553 elif self.host is not None and self.path:
1562 1554 path = '/' + path
1563 1555 # We also need to handle the case of file:///C:/, which
1564 1556 # should return C:/, not /C:/.
1565 1557 elif hasdriveletter(path):
1566 1558 # Strip leading slash from paths with drive names
1567 1559 return path[1:]
1568 1560 return path
1569 1561 return self._origpath
1570 1562
1571 1563 def hasscheme(path):
1572 1564 return bool(url(path).scheme)
1573 1565
1574 1566 def hasdriveletter(path):
1575 1567 return path[1:2] == ':' and path[0:1].isalpha()
1576 1568
1577 1569 def urllocalpath(path):
1578 1570 return url(path, parsequery=False, parsefragment=False).localpath()
1579 1571
1580 1572 def hidepassword(u):
1581 1573 '''hide user credential in a url string'''
1582 1574 u = url(u)
1583 1575 if u.passwd:
1584 1576 u.passwd = '***'
1585 1577 return str(u)
1586 1578
1587 1579 def removeauth(u):
1588 1580 '''remove all authentication information from a url string'''
1589 1581 u = url(u)
1590 1582 u.user = u.passwd = None
1591 1583 return str(u)
1592 1584
1593 1585 def isatty(fd):
1594 1586 try:
1595 1587 return fd.isatty()
1596 1588 except AttributeError:
1597 1589 return False
General Comments 0
You need to be logged in to leave comments. Login now