135
|
1 # windows.py - Windows utility function implementations for Mercurial
|
|
2 #
|
|
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
|
|
4 #
|
|
5 # This software may be used and distributed according to the terms of the
|
|
6 # GNU General Public License version 2, incorporated herein by reference.
|
|
7
|
|
8 from i18n import _
|
|
9 import osutil, error
|
|
10 import errno, msvcrt, os, re, sys
|
|
11
|
|
12 nulldev = 'NUL:'
|
|
13 umask = 002
|
|
14
|
|
15 # wrap osutil.posixfile to provide friendlier exceptions
|
|
16 def posixfile(name, mode='r', buffering=-1):
|
|
17 try:
|
|
18 return osutil.posixfile(name, mode, buffering)
|
|
19 except WindowsError, err:
|
|
20 raise IOError(err.errno, err.strerror)
|
|
21 posixfile.__doc__ = osutil.posixfile.__doc__
|
|
22
|
|
23 class winstdout(object):
|
|
24 '''stdout on windows misbehaves if sent through a pipe'''
|
|
25
|
|
26 def __init__(self, fp):
|
|
27 self.fp = fp
|
|
28
|
|
29 def __getattr__(self, key):
|
|
30 return getattr(self.fp, key)
|
|
31
|
|
32 def close(self):
|
|
33 try:
|
|
34 self.fp.close()
|
|
35 except: pass
|
|
36
|
|
37 def write(self, s):
|
|
38 try:
|
|
39 # This is workaround for "Not enough space" error on
|
|
40 # writing large size of data to console.
|
|
41 limit = 16000
|
|
42 l = len(s)
|
|
43 start = 0
|
|
44 self.softspace = 0;
|
|
45 while start < l:
|
|
46 end = start + limit
|
|
47 self.fp.write(s[start:end])
|
|
48 start = end
|
|
49 except IOError, inst:
|
|
50 if inst.errno != 0: raise
|
|
51 self.close()
|
|
52 raise IOError(errno.EPIPE, 'Broken pipe')
|
|
53
|
|
54 def flush(self):
|
|
55 try:
|
|
56 return self.fp.flush()
|
|
57 except IOError, inst:
|
|
58 if inst.errno != errno.EINVAL: raise
|
|
59 self.close()
|
|
60 raise IOError(errno.EPIPE, 'Broken pipe')
|
|
61
|
|
62 sys.stdout = winstdout(sys.stdout)
|
|
63
|
|
64 def _is_win_9x():
|
|
65 '''return true if run on windows 95, 98 or me.'''
|
|
66 try:
|
|
67 return sys.getwindowsversion()[3] == 1
|
|
68 except AttributeError:
|
|
69 return 'command' in os.environ.get('comspec', '')
|
|
70
|
|
71 def openhardlinks():
|
|
72 return not _is_win_9x() and "win32api" in globals()
|
|
73
|
|
74 def system_rcpath():
|
|
75 try:
|
|
76 return system_rcpath_win32()
|
|
77 except:
|
|
78 return [r'c:\mercurial\mercurial.ini']
|
|
79
|
|
80 def user_rcpath():
|
|
81 '''return os-specific hgrc search path to the user dir'''
|
|
82 try:
|
|
83 path = user_rcpath_win32()
|
|
84 except:
|
|
85 home = os.path.expanduser('~')
|
|
86 path = [os.path.join(home, 'mercurial.ini'),
|
|
87 os.path.join(home, '.hgrc')]
|
|
88 userprofile = os.environ.get('USERPROFILE')
|
|
89 if userprofile:
|
|
90 path.append(os.path.join(userprofile, 'mercurial.ini'))
|
|
91 path.append(os.path.join(userprofile, '.hgrc'))
|
|
92 return path
|
|
93
|
|
94 def parse_patch_output(output_line):
|
|
95 """parses the output produced by patch and returns the filename"""
|
|
96 pf = output_line[14:]
|
|
97 if pf[0] == '`':
|
|
98 pf = pf[1:-1] # Remove the quotes
|
|
99 return pf
|
|
100
|
|
101 def sshargs(sshcmd, host, user, port):
|
|
102 '''Build argument list for ssh or Plink'''
|
|
103 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
|
|
104 args = user and ("%s@%s" % (user, host)) or host
|
|
105 return port and ("%s %s %s" % (args, pflag, port)) or args
|
|
106
|
|
107 def testpid(pid):
|
|
108 '''return False if pid dead, True if running or not known'''
|
|
109 return True
|
|
110
|
|
111 def set_flags(f, l, x):
|
|
112 pass
|
|
113
|
|
114 def set_binary(fd):
|
|
115 # When run without console, pipes may expose invalid
|
|
116 # fileno(), usually set to -1.
|
|
117 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
|
|
118 msvcrt.setmode(fd.fileno(), os.O_BINARY)
|
|
119
|
|
120 def pconvert(path):
|
|
121 return '/'.join(path.split(os.sep))
|
|
122
|
|
123 def localpath(path):
|
|
124 return path.replace('/', '\\')
|
|
125
|
|
126 def normpath(path):
|
|
127 return pconvert(os.path.normpath(path))
|
|
128
|
|
129 def samestat(s1, s2):
|
|
130 return False
|
|
131
|
|
132 # A sequence of backslashes is special iff it precedes a double quote:
|
|
133 # - if there's an even number of backslashes, the double quote is not
|
|
134 # quoted (i.e. it ends the quoted region)
|
|
135 # - if there's an odd number of backslashes, the double quote is quoted
|
|
136 # - in both cases, every pair of backslashes is unquoted into a single
|
|
137 # backslash
|
|
138 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
|
|
139 # So, to quote a string, we must surround it in double quotes, double
|
|
140 # the number of backslashes that preceed double quotes and add another
|
|
141 # backslash before every double quote (being careful with the double
|
|
142 # quote we've appended to the end)
|
|
143 _quotere = None
|
|
144 def shellquote(s):
|
|
145 global _quotere
|
|
146 if _quotere is None:
|
|
147 _quotere = re.compile(r'(\\*)("|\\$)')
|
|
148 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
|
|
149
|
|
150 def quotecommand(cmd):
|
|
151 """Build a command string suitable for os.popen* calls."""
|
|
152 # The extra quotes are needed because popen* runs the command
|
|
153 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
|
|
154 return '"' + cmd + '"'
|
|
155
|
|
156 def popen(command, mode='r'):
|
|
157 # Work around "popen spawned process may not write to stdout
|
|
158 # under windows"
|
|
159 # http://bugs.python.org/issue1366
|
|
160 command += " 2> %s" % nulldev
|
|
161 return os.popen(quotecommand(command), mode)
|
|
162
|
|
163 def explain_exit(code):
|
|
164 return _("exited with status %d") % code, code
|
|
165
|
|
166 # if you change this stub into a real check, please try to implement the
|
|
167 # username and groupname functions above, too.
|
|
168 def isowner(st):
|
|
169 return True
|
|
170
|
|
171 def find_exe(command):
|
|
172 '''Find executable for command searching like cmd.exe does.
|
|
173 If command is a basename then PATH is searched for command.
|
|
174 PATH isn't searched if command is an absolute or relative path.
|
|
175 An extension from PATHEXT is found and added if not present.
|
|
176 If command isn't found None is returned.'''
|
|
177 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
|
|
178 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
|
|
179 if os.path.splitext(command)[1].lower() in pathexts:
|
|
180 pathexts = ['']
|
|
181
|
|
182 def findexisting(pathcommand):
|
|
183 'Will append extension (if needed) and return existing file'
|
|
184 for ext in pathexts:
|
|
185 executable = pathcommand + ext
|
|
186 if os.path.exists(executable):
|
|
187 return executable
|
|
188 return None
|
|
189
|
|
190 if os.sep in command:
|
|
191 return findexisting(command)
|
|
192
|
|
193 for path in os.environ.get('PATH', '').split(os.pathsep):
|
|
194 executable = findexisting(os.path.join(path, command))
|
|
195 if executable is not None:
|
|
196 return executable
|
|
197 return None
|
|
198
|
|
199 def set_signal_handler():
|
|
200 try:
|
|
201 set_signal_handler_win32()
|
|
202 except NameError:
|
|
203 pass
|
|
204
|
|
205 def statfiles(files):
|
|
206 '''Stat each file in files and yield stat or None if file does not exist.
|
|
207 Cluster and cache stat per directory to minimize number of OS stat calls.'''
|
|
208 ncase = os.path.normcase
|
|
209 sep = os.sep
|
|
210 dircache = {} # dirname -> filename -> status | None if file does not exist
|
|
211 for nf in files:
|
|
212 nf = ncase(nf)
|
|
213 dir, base = os.path.split(nf)
|
|
214 if not dir:
|
|
215 dir = '.'
|
|
216 cache = dircache.get(dir, None)
|
|
217 if cache is None:
|
|
218 try:
|
|
219 dmap = dict([(ncase(n), s)
|
|
220 for n, k, s in osutil.listdir(dir, True)])
|
|
221 except OSError, err:
|
|
222 # handle directory not found in Python version prior to 2.5
|
|
223 # Python <= 2.4 returns native Windows code 3 in errno
|
|
224 # Python >= 2.5 returns ENOENT and adds winerror field
|
|
225 # EINVAL is raised if dir is not a directory.
|
|
226 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
|
|
227 errno.ENOTDIR):
|
|
228 raise
|
|
229 dmap = {}
|
|
230 cache = dircache.setdefault(dir, dmap)
|
|
231 yield cache.get(base, None)
|
|
232
|
|
233 def getuser():
|
|
234 '''return name of current user'''
|
|
235 raise error.Abort(_('user name not available - set USERNAME '
|
|
236 'environment variable'))
|
|
237
|
|
238 def username(uid=None):
|
|
239 """Return the name of the user with the given uid.
|
|
240
|
|
241 If uid is None, return the name of the current user."""
|
|
242 return None
|
|
243
|
|
244 def groupname(gid=None):
|
|
245 """Return the name of the group with the given gid.
|
|
246
|
|
247 If gid is None, return the name of the current group."""
|
|
248 return None
|
|
249
|
|
250 def _removedirs(name):
|
|
251 """special version of os.removedirs that does not remove symlinked
|
|
252 directories or junction points if they actually contain files"""
|
|
253 if osutil.listdir(name):
|
|
254 return
|
|
255 os.rmdir(name)
|
|
256 head, tail = os.path.split(name)
|
|
257 if not tail:
|
|
258 head, tail = os.path.split(head)
|
|
259 while head and tail:
|
|
260 try:
|
|
261 if osutil.listdir(name):
|
|
262 return
|
|
263 os.rmdir(head)
|
|
264 except:
|
|
265 break
|
|
266 head, tail = os.path.split(head)
|
|
267
|
|
268 def unlink(f):
|
|
269 """unlink and remove the directory if it is empty"""
|
|
270 os.unlink(f)
|
|
271 # try removing directories that might now be empty
|
|
272 try:
|
|
273 _removedirs(os.path.dirname(f))
|
|
274 except OSError:
|
|
275 pass
|
|
276
|
|
277 try:
|
|
278 # override functions with win32 versions if possible
|
|
279 from win32 import *
|
|
280 except ImportError:
|
|
281 pass
|
|
282
|
|
283 expandglobs = True
|