##// END OF EJS Templates
port win32.py to using the Python ctypes library...
Adrian Buehlmann -
r13375:f1fa8f48 default
parent child Browse files
Show More
@@ -1,40 +1,31 b''
1 <?xml version="1.0" encoding="utf-8"?>
1 <?xml version="1.0" encoding="utf-8"?>
2 <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
2 <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
3
3
4 <?include guids.wxi ?>
4 <?include guids.wxi ?>
5 <?include defines.wxi ?>
5 <?include defines.wxi ?>
6
6
7 <Fragment>
7 <Fragment>
8 <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
8 <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
9 <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'>
9 <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'>
10 <File Name="library.zip" KeyPath="yes" />
10 <File Name="library.zip" KeyPath="yes" />
11 <File Name="mercurial.base85.pyd" />
11 <File Name="mercurial.base85.pyd" />
12 <File Name="mercurial.bdiff.pyd" />
12 <File Name="mercurial.bdiff.pyd" />
13 <File Name="mercurial.diffhelpers.pyd" />
13 <File Name="mercurial.diffhelpers.pyd" />
14 <File Name="mercurial.mpatch.pyd" />
14 <File Name="mercurial.mpatch.pyd" />
15 <File Name="mercurial.osutil.pyd" />
15 <File Name="mercurial.osutil.pyd" />
16 <File Name="mercurial.parsers.pyd" />
16 <File Name="mercurial.parsers.pyd" />
17 <File Name="pyexpat.pyd" />
17 <File Name="pyexpat.pyd" />
18 <File Name="python26.dll" />
18 <File Name="python26.dll" />
19 <File Name="pythoncom26.dll" />
20 <File Name="pywintypes26.dll" />
21 <File Name="bz2.pyd" />
19 <File Name="bz2.pyd" />
22 <File Name="select.pyd" />
20 <File Name="select.pyd" />
23 <File Name="unicodedata.pyd" />
21 <File Name="unicodedata.pyd" />
24 <File Name="win32api.pyd" />
22 <File Name="_ctypes.pyd" />
25 <File Name="win32com.shell.shell.pyd" />
26 <File Name="win32console.pyd" />
27 <File Name="win32file.pyd" />
28 <File Name="win32gui.pyd" />
29 <File Name="win32pipe.pyd" />
30 <File Name="win32process.pyd" />
31 <File Name="_elementtree.pyd" />
23 <File Name="_elementtree.pyd" />
32 <File Name="_hashlib.pyd" />
24 <File Name="_hashlib.pyd" />
33 <File Name="_socket.pyd" />
25 <File Name="_socket.pyd" />
34 <File Name="_ssl.pyd" />
26 <File Name="_ssl.pyd" />
35 <File Name="_win32sysloader.pyd" />
36 </Component>
27 </Component>
37 </DirectoryRef>
28 </DirectoryRef>
38 </Fragment>
29 </Fragment>
39
30
40 </Wix>
31 </Wix>
@@ -1,52 +1,52 b''
1 <Include>
1 <Include>
2 <!-- These are component GUIDs used for Mercurial installers.
2 <!-- These are component GUIDs used for Mercurial installers.
3 YOU MUST CHANGE ALL GUIDs below when copying this file
3 YOU MUST CHANGE ALL GUIDs below when copying this file
4 and replace 'Mercurial' in this notice with the name of
4 and replace 'Mercurial' in this notice with the name of
5 your project. Component GUIDs have global namespace! -->
5 your project. Component GUIDs have global namespace! -->
6
6
7 <!-- contrib.wxs -->
7 <!-- contrib.wxs -->
8 <?define contrib.guid = {F17D27B7-4A6B-4cd2-AE72-FED3CFAA585E} ?>
8 <?define contrib.guid = {F17D27B7-4A6B-4cd2-AE72-FED3CFAA585E} ?>
9 <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
9 <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
10
10
11 <!-- dist.wxs -->
11 <!-- dist.wxs -->
12 <?define dist.guid = {0F63D160-0740-4BAF-BF25-0C6930310F51} ?>
12 <?define dist.guid = {C3B634A4-1B05-4A40-94A9-38EE853CF693} ?>
13
13
14 <!-- doc.wxs -->
14 <!-- doc.wxs -->
15 <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
15 <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
16 <?define doc.hgignore.5.html.guid = {AA9118C4-F3A0-4429-A5F4-5A1906B2D67F} ?>
16 <?define doc.hgignore.5.html.guid = {AA9118C4-F3A0-4429-A5F4-5A1906B2D67F} ?>
17 <?define doc.hgrc.5.html = {E0CEA1EB-FA01-408c-844B-EE5965165BAE} ?>
17 <?define doc.hgrc.5.html = {E0CEA1EB-FA01-408c-844B-EE5965165BAE} ?>
18 <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?>
18 <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?>
19
19
20 <!-- help.wxs -->
20 <!-- help.wxs -->
21 <?define helpFolder.guid = {21FE9CF9-933E-4C2E-B2EC-413A569FB996} ?>
21 <?define helpFolder.guid = {21FE9CF9-933E-4C2E-B2EC-413A569FB996} ?>
22
22
23 <!-- i18n.wxs -->
23 <!-- i18n.wxs -->
24 <?define i18nFolder.guid = {EADFA693-A0B5-4f31-87C9-3997CFAC1B42} ?>
24 <?define i18nFolder.guid = {EADFA693-A0B5-4f31-87C9-3997CFAC1B42} ?>
25
25
26 <!-- templates.wxs -->
26 <!-- templates.wxs -->
27 <?define templates.root.guid = {111509CB-4C96-4035-80BC-F66A99CD5ACB} ?>
27 <?define templates.root.guid = {111509CB-4C96-4035-80BC-F66A99CD5ACB} ?>
28 <?define templates.atom.guid = {45FCDF84-DE27-44f4-AF6C-C41F5994AE0D} ?>
28 <?define templates.atom.guid = {45FCDF84-DE27-44f4-AF6C-C41F5994AE0D} ?>
29 <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
29 <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
30 <?define templates.gitweb.guid = {D8BFE3ED-06DD-4C4D-A00D-6D825955F922} ?>
30 <?define templates.gitweb.guid = {D8BFE3ED-06DD-4C4D-A00D-6D825955F922} ?>
31 <?define templates.monoblue.guid = {A394B4D5-2AF7-4AAC-AEA8-E92176E5501E} ?>
31 <?define templates.monoblue.guid = {A394B4D5-2AF7-4AAC-AEA8-E92176E5501E} ?>
32 <?define templates.paper.guid = {7C94B80D-FD0D-44E7-8489-F30A9E20A47F} ?>
32 <?define templates.paper.guid = {7C94B80D-FD0D-44E7-8489-F30A9E20A47F} ?>
33 <?define templates.raw.guid = {04DE03A2-FBFD-4c5f-8DEA-5436DDF4689D} ?>
33 <?define templates.raw.guid = {04DE03A2-FBFD-4c5f-8DEA-5436DDF4689D} ?>
34 <?define templates.rss.guid = {A7D608DE-0CF6-44f4-AF1E-EE30CC237FDA} ?>
34 <?define templates.rss.guid = {A7D608DE-0CF6-44f4-AF1E-EE30CC237FDA} ?>
35 <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?>
35 <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?>
36 <?define templates.static.guid = {68C9F843-DE7E-480f-9DA2-D220B19D02C3} ?>
36 <?define templates.static.guid = {68C9F843-DE7E-480f-9DA2-D220B19D02C3} ?>
37
37
38 <!-- mercurial.wxs -->
38 <!-- mercurial.wxs -->
39 <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
39 <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
40
40
41 <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
41 <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
42
42
43 <?define ReadMe.guid = {56A8E372-991D-4DCA-B91D-93D775974CF5} ?>
43 <?define ReadMe.guid = {56A8E372-991D-4DCA-B91D-93D775974CF5} ?>
44 <?define COPYING.guid = {B7801DBA-1C49-4BF4-91AD-33C65F5C7895} ?>
44 <?define COPYING.guid = {B7801DBA-1C49-4BF4-91AD-33C65F5C7895} ?>
45 <?define mercurial.rc.guid = {1D5FAEEE-7E6E-43B1-9F7F-802714316B15} ?>
45 <?define mercurial.rc.guid = {1D5FAEEE-7E6E-43B1-9F7F-802714316B15} ?>
46 <?define mergetools.rc.guid = {E8A1DC29-FF40-4B5F-BD12-80B9F7BF0CCD} ?>
46 <?define mergetools.rc.guid = {E8A1DC29-FF40-4B5F-BD12-80B9F7BF0CCD} ?>
47 <?define paths.rc.guid = {F9ADF21D-5F0B-4934-8CD9-14BE63664721} ?>
47 <?define paths.rc.guid = {F9ADF21D-5F0B-4934-8CD9-14BE63664721} ?>
48 <?define cacert.pem.guid = {EC1B2630-FE21-46E6-915B-A6545AF703D4} ?>
48 <?define cacert.pem.guid = {EC1B2630-FE21-46E6-915B-A6545AF703D4} ?>
49 <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
49 <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
50 <?define hgcmd.guid = {65CCC756-E72E-4C5F-901E-D575EDC80DB3} ?>
50 <?define hgcmd.guid = {65CCC756-E72E-4C5F-901E-D575EDC80DB3} ?>
51
51
52 </Include>
52 </Include>
@@ -1,298 +1,303 b''
1 # posix.py - Posix utility function implementations for Mercurial
1 # posix.py - Posix utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import osutil
9 import osutil
10 import os, sys, errno, stat, getpass, pwd, grp
10 import os, sys, errno, stat, getpass, pwd, grp
11
11
12 posixfile = open
12 posixfile = open
13 nulldev = '/dev/null'
13 nulldev = '/dev/null'
14 normpath = os.path.normpath
14 normpath = os.path.normpath
15 samestat = os.path.samestat
15 samestat = os.path.samestat
16 os_link = os.link
16 unlink = os.unlink
17 unlink = os.unlink
17 rename = os.rename
18 rename = os.rename
18 expandglobs = False
19 expandglobs = False
19
20
20 umask = os.umask(0)
21 umask = os.umask(0)
21 os.umask(umask)
22 os.umask(umask)
22
23
23 def openhardlinks():
24 def openhardlinks():
24 '''return true if it is safe to hold open file handles to hardlinks'''
25 '''return true if it is safe to hold open file handles to hardlinks'''
25 return True
26 return True
26
27
28 def nlinks(name):
29 '''return number of hardlinks for the given file'''
30 return os.lstat(name).st_nlink
31
27 def rcfiles(path):
32 def rcfiles(path):
28 rcs = [os.path.join(path, 'hgrc')]
33 rcs = [os.path.join(path, 'hgrc')]
29 rcdir = os.path.join(path, 'hgrc.d')
34 rcdir = os.path.join(path, 'hgrc.d')
30 try:
35 try:
31 rcs.extend([os.path.join(rcdir, f)
36 rcs.extend([os.path.join(rcdir, f)
32 for f, kind in osutil.listdir(rcdir)
37 for f, kind in osutil.listdir(rcdir)
33 if f.endswith(".rc")])
38 if f.endswith(".rc")])
34 except OSError:
39 except OSError:
35 pass
40 pass
36 return rcs
41 return rcs
37
42
38 def system_rcpath():
43 def system_rcpath():
39 path = []
44 path = []
40 # old mod_python does not set sys.argv
45 # old mod_python does not set sys.argv
41 if len(getattr(sys, 'argv', [])) > 0:
46 if len(getattr(sys, 'argv', [])) > 0:
42 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
47 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
43 '/../etc/mercurial'))
48 '/../etc/mercurial'))
44 path.extend(rcfiles('/etc/mercurial'))
49 path.extend(rcfiles('/etc/mercurial'))
45 return path
50 return path
46
51
47 def user_rcpath():
52 def user_rcpath():
48 return [os.path.expanduser('~/.hgrc')]
53 return [os.path.expanduser('~/.hgrc')]
49
54
50 def parse_patch_output(output_line):
55 def parse_patch_output(output_line):
51 """parses the output produced by patch and returns the filename"""
56 """parses the output produced by patch and returns the filename"""
52 pf = output_line[14:]
57 pf = output_line[14:]
53 if os.sys.platform == 'OpenVMS':
58 if os.sys.platform == 'OpenVMS':
54 if pf[0] == '`':
59 if pf[0] == '`':
55 pf = pf[1:-1] # Remove the quotes
60 pf = pf[1:-1] # Remove the quotes
56 else:
61 else:
57 if pf.startswith("'") and pf.endswith("'") and " " in pf:
62 if pf.startswith("'") and pf.endswith("'") and " " in pf:
58 pf = pf[1:-1] # Remove the quotes
63 pf = pf[1:-1] # Remove the quotes
59 return pf
64 return pf
60
65
61 def sshargs(sshcmd, host, user, port):
66 def sshargs(sshcmd, host, user, port):
62 '''Build argument list for ssh'''
67 '''Build argument list for ssh'''
63 args = user and ("%s@%s" % (user, host)) or host
68 args = user and ("%s@%s" % (user, host)) or host
64 return port and ("%s -p %s" % (args, port)) or args
69 return port and ("%s -p %s" % (args, port)) or args
65
70
66 def is_exec(f):
71 def is_exec(f):
67 """check whether a file is executable"""
72 """check whether a file is executable"""
68 return (os.lstat(f).st_mode & 0100 != 0)
73 return (os.lstat(f).st_mode & 0100 != 0)
69
74
70 def set_flags(f, l, x):
75 def set_flags(f, l, x):
71 s = os.lstat(f).st_mode
76 s = os.lstat(f).st_mode
72 if l:
77 if l:
73 if not stat.S_ISLNK(s):
78 if not stat.S_ISLNK(s):
74 # switch file to link
79 # switch file to link
75 data = open(f).read()
80 data = open(f).read()
76 os.unlink(f)
81 os.unlink(f)
77 try:
82 try:
78 os.symlink(data, f)
83 os.symlink(data, f)
79 except:
84 except:
80 # failed to make a link, rewrite file
85 # failed to make a link, rewrite file
81 open(f, "w").write(data)
86 open(f, "w").write(data)
82 # no chmod needed at this point
87 # no chmod needed at this point
83 return
88 return
84 if stat.S_ISLNK(s):
89 if stat.S_ISLNK(s):
85 # switch link to file
90 # switch link to file
86 data = os.readlink(f)
91 data = os.readlink(f)
87 os.unlink(f)
92 os.unlink(f)
88 open(f, "w").write(data)
93 open(f, "w").write(data)
89 s = 0666 & ~umask # avoid restatting for chmod
94 s = 0666 & ~umask # avoid restatting for chmod
90
95
91 sx = s & 0100
96 sx = s & 0100
92 if x and not sx:
97 if x and not sx:
93 # Turn on +x for every +r bit when making a file executable
98 # Turn on +x for every +r bit when making a file executable
94 # and obey umask.
99 # and obey umask.
95 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
100 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
96 elif not x and sx:
101 elif not x and sx:
97 # Turn off all +x bits
102 # Turn off all +x bits
98 os.chmod(f, s & 0666)
103 os.chmod(f, s & 0666)
99
104
100 def set_binary(fd):
105 def set_binary(fd):
101 pass
106 pass
102
107
103 def pconvert(path):
108 def pconvert(path):
104 return path
109 return path
105
110
106 def localpath(path):
111 def localpath(path):
107 return path
112 return path
108
113
109 def samefile(fpath1, fpath2):
114 def samefile(fpath1, fpath2):
110 """Returns whether path1 and path2 refer to the same file. This is only
115 """Returns whether path1 and path2 refer to the same file. This is only
111 guaranteed to work for files, not directories."""
116 guaranteed to work for files, not directories."""
112 return os.path.samefile(fpath1, fpath2)
117 return os.path.samefile(fpath1, fpath2)
113
118
114 def samedevice(fpath1, fpath2):
119 def samedevice(fpath1, fpath2):
115 """Returns whether fpath1 and fpath2 are on the same device. This is only
120 """Returns whether fpath1 and fpath2 are on the same device. This is only
116 guaranteed to work for files, not directories."""
121 guaranteed to work for files, not directories."""
117 st1 = os.lstat(fpath1)
122 st1 = os.lstat(fpath1)
118 st2 = os.lstat(fpath2)
123 st2 = os.lstat(fpath2)
119 return st1.st_dev == st2.st_dev
124 return st1.st_dev == st2.st_dev
120
125
121 if sys.platform == 'darwin':
126 if sys.platform == 'darwin':
122 import fcntl # only needed on darwin, missing on jython
127 import fcntl # only needed on darwin, missing on jython
123 def realpath(path):
128 def realpath(path):
124 '''
129 '''
125 Returns the true, canonical file system path equivalent to the given
130 Returns the true, canonical file system path equivalent to the given
126 path.
131 path.
127
132
128 Equivalent means, in this case, resulting in the same, unique
133 Equivalent means, in this case, resulting in the same, unique
129 file system link to the path. Every file system entry, whether a file,
134 file system link to the path. Every file system entry, whether a file,
130 directory, hard link or symbolic link or special, will have a single
135 directory, hard link or symbolic link or special, will have a single
131 path preferred by the system, but may allow multiple, differing path
136 path preferred by the system, but may allow multiple, differing path
132 lookups to point to it.
137 lookups to point to it.
133
138
134 Most regular UNIX file systems only allow a file system entry to be
139 Most regular UNIX file systems only allow a file system entry to be
135 looked up by its distinct path. Obviously, this does not apply to case
140 looked up by its distinct path. Obviously, this does not apply to case
136 insensitive file systems, whether case preserving or not. The most
141 insensitive file systems, whether case preserving or not. The most
137 complex issue to deal with is file systems transparently reencoding the
142 complex issue to deal with is file systems transparently reencoding the
138 path, such as the non-standard Unicode normalisation required for HFS+
143 path, such as the non-standard Unicode normalisation required for HFS+
139 and HFSX.
144 and HFSX.
140 '''
145 '''
141 # Constants copied from /usr/include/sys/fcntl.h
146 # Constants copied from /usr/include/sys/fcntl.h
142 F_GETPATH = 50
147 F_GETPATH = 50
143 O_SYMLINK = 0x200000
148 O_SYMLINK = 0x200000
144
149
145 try:
150 try:
146 fd = os.open(path, O_SYMLINK)
151 fd = os.open(path, O_SYMLINK)
147 except OSError, err:
152 except OSError, err:
148 if err.errno == errno.ENOENT:
153 if err.errno == errno.ENOENT:
149 return path
154 return path
150 raise
155 raise
151
156
152 try:
157 try:
153 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
158 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
154 finally:
159 finally:
155 os.close(fd)
160 os.close(fd)
156 else:
161 else:
157 # Fallback to the likely inadequate Python builtin function.
162 # Fallback to the likely inadequate Python builtin function.
158 realpath = os.path.realpath
163 realpath = os.path.realpath
159
164
160 def shellquote(s):
165 def shellquote(s):
161 if os.sys.platform == 'OpenVMS':
166 if os.sys.platform == 'OpenVMS':
162 return '"%s"' % s
167 return '"%s"' % s
163 else:
168 else:
164 return "'%s'" % s.replace("'", "'\\''")
169 return "'%s'" % s.replace("'", "'\\''")
165
170
166 def quotecommand(cmd):
171 def quotecommand(cmd):
167 return cmd
172 return cmd
168
173
169 def popen(command, mode='r'):
174 def popen(command, mode='r'):
170 return os.popen(command, mode)
175 return os.popen(command, mode)
171
176
172 def testpid(pid):
177 def testpid(pid):
173 '''return False if pid dead, True if running or not sure'''
178 '''return False if pid dead, True if running or not sure'''
174 if os.sys.platform == 'OpenVMS':
179 if os.sys.platform == 'OpenVMS':
175 return True
180 return True
176 try:
181 try:
177 os.kill(pid, 0)
182 os.kill(pid, 0)
178 return True
183 return True
179 except OSError, inst:
184 except OSError, inst:
180 return inst.errno != errno.ESRCH
185 return inst.errno != errno.ESRCH
181
186
182 def explain_exit(code):
187 def explain_exit(code):
183 """return a 2-tuple (desc, code) describing a subprocess status
188 """return a 2-tuple (desc, code) describing a subprocess status
184 (codes from kill are negative - not os.system/wait encoding)"""
189 (codes from kill are negative - not os.system/wait encoding)"""
185 if code >= 0:
190 if code >= 0:
186 return _("exited with status %d") % code, code
191 return _("exited with status %d") % code, code
187 return _("killed by signal %d") % -code, -code
192 return _("killed by signal %d") % -code, -code
188
193
189 def isowner(st):
194 def isowner(st):
190 """Return True if the stat object st is from the current user."""
195 """Return True if the stat object st is from the current user."""
191 return st.st_uid == os.getuid()
196 return st.st_uid == os.getuid()
192
197
193 def find_exe(command):
198 def find_exe(command):
194 '''Find executable for command searching like which does.
199 '''Find executable for command searching like which does.
195 If command is a basename then PATH is searched for command.
200 If command is a basename then PATH is searched for command.
196 PATH isn't searched if command is an absolute or relative path.
201 PATH isn't searched if command is an absolute or relative path.
197 If command isn't found None is returned.'''
202 If command isn't found None is returned.'''
198 if sys.platform == 'OpenVMS':
203 if sys.platform == 'OpenVMS':
199 return command
204 return command
200
205
201 def findexisting(executable):
206 def findexisting(executable):
202 'Will return executable if existing file'
207 'Will return executable if existing file'
203 if os.path.exists(executable):
208 if os.path.exists(executable):
204 return executable
209 return executable
205 return None
210 return None
206
211
207 if os.sep in command:
212 if os.sep in command:
208 return findexisting(command)
213 return findexisting(command)
209
214
210 for path in os.environ.get('PATH', '').split(os.pathsep):
215 for path in os.environ.get('PATH', '').split(os.pathsep):
211 executable = findexisting(os.path.join(path, command))
216 executable = findexisting(os.path.join(path, command))
212 if executable is not None:
217 if executable is not None:
213 return executable
218 return executable
214 return None
219 return None
215
220
216 def set_signal_handler():
221 def set_signal_handler():
217 pass
222 pass
218
223
219 def statfiles(files):
224 def statfiles(files):
220 'Stat each file in files and yield stat or None if file does not exist.'
225 'Stat each file in files and yield stat or None if file does not exist.'
221 lstat = os.lstat
226 lstat = os.lstat
222 for nf in files:
227 for nf in files:
223 try:
228 try:
224 st = lstat(nf)
229 st = lstat(nf)
225 except OSError, err:
230 except OSError, err:
226 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
231 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
227 raise
232 raise
228 st = None
233 st = None
229 yield st
234 yield st
230
235
231 def getuser():
236 def getuser():
232 '''return name of current user'''
237 '''return name of current user'''
233 return getpass.getuser()
238 return getpass.getuser()
234
239
235 def expand_glob(pats):
240 def expand_glob(pats):
236 '''On Windows, expand the implicit globs in a list of patterns'''
241 '''On Windows, expand the implicit globs in a list of patterns'''
237 return list(pats)
242 return list(pats)
238
243
239 def username(uid=None):
244 def username(uid=None):
240 """Return the name of the user with the given uid.
245 """Return the name of the user with the given uid.
241
246
242 If uid is None, return the name of the current user."""
247 If uid is None, return the name of the current user."""
243
248
244 if uid is None:
249 if uid is None:
245 uid = os.getuid()
250 uid = os.getuid()
246 try:
251 try:
247 return pwd.getpwuid(uid)[0]
252 return pwd.getpwuid(uid)[0]
248 except KeyError:
253 except KeyError:
249 return str(uid)
254 return str(uid)
250
255
251 def groupname(gid=None):
256 def groupname(gid=None):
252 """Return the name of the group with the given gid.
257 """Return the name of the group with the given gid.
253
258
254 If gid is None, return the name of the current group."""
259 If gid is None, return the name of the current group."""
255
260
256 if gid is None:
261 if gid is None:
257 gid = os.getgid()
262 gid = os.getgid()
258 try:
263 try:
259 return grp.getgrgid(gid)[0]
264 return grp.getgrgid(gid)[0]
260 except KeyError:
265 except KeyError:
261 return str(gid)
266 return str(gid)
262
267
263 def groupmembers(name):
268 def groupmembers(name):
264 """Return the list of members of the group with the given
269 """Return the list of members of the group with the given
265 name, KeyError if the group does not exist.
270 name, KeyError if the group does not exist.
266 """
271 """
267 return list(grp.getgrnam(name).gr_mem)
272 return list(grp.getgrnam(name).gr_mem)
268
273
269 def spawndetached(args):
274 def spawndetached(args):
270 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
275 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
271 args[0], args)
276 args[0], args)
272
277
273 def gethgcmd():
278 def gethgcmd():
274 return sys.argv[:1]
279 return sys.argv[:1]
275
280
276 def termwidth():
281 def termwidth():
277 try:
282 try:
278 import termios, array, fcntl
283 import termios, array, fcntl
279 for dev in (sys.stderr, sys.stdout, sys.stdin):
284 for dev in (sys.stderr, sys.stdout, sys.stdin):
280 try:
285 try:
281 try:
286 try:
282 fd = dev.fileno()
287 fd = dev.fileno()
283 except AttributeError:
288 except AttributeError:
284 continue
289 continue
285 if not os.isatty(fd):
290 if not os.isatty(fd):
286 continue
291 continue
287 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
292 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
288 return array.array('h', arri)[1]
293 return array.array('h', arri)[1]
289 except ValueError:
294 except ValueError:
290 pass
295 pass
291 except IOError, e:
296 except IOError, e:
292 if e[0] == errno.EINVAL:
297 if e[0] == errno.EINVAL:
293 pass
298 pass
294 else:
299 else:
295 raise
300 raise
296 except ImportError:
301 except ImportError:
297 pass
302 pass
298 return 80
303 return 80
@@ -1,1555 +1,1545 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, textwrap, unicodedata, signal
19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 import imp, socket
20 import imp, socket
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
31 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 else:
33 else:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explain_exit(code)))
200 (cmd, explain_exit(code)))
201 return open(outname, 'rb').read()
201 return open(outname, 'rb').read()
202 finally:
202 finally:
203 try:
203 try:
204 if inname:
204 if inname:
205 os.unlink(inname)
205 os.unlink(inname)
206 except:
206 except:
207 pass
207 pass
208 try:
208 try:
209 if outname:
209 if outname:
210 os.unlink(outname)
210 os.unlink(outname)
211 except:
211 except:
212 pass
212 pass
213
213
214 filtertable = {
214 filtertable = {
215 'tempfile:': tempfilter,
215 'tempfile:': tempfilter,
216 'pipe:': pipefilter,
216 'pipe:': pipefilter,
217 }
217 }
218
218
219 def filter(s, cmd):
219 def filter(s, cmd):
220 "filter a string through a command that transforms its input to its output"
220 "filter a string through a command that transforms its input to its output"
221 for name, fn in filtertable.iteritems():
221 for name, fn in filtertable.iteritems():
222 if cmd.startswith(name):
222 if cmd.startswith(name):
223 return fn(s, cmd[len(name):].lstrip())
223 return fn(s, cmd[len(name):].lstrip())
224 return pipefilter(s, cmd)
224 return pipefilter(s, cmd)
225
225
226 def binary(s):
226 def binary(s):
227 """return true if a string is binary data"""
227 """return true if a string is binary data"""
228 return bool(s and '\0' in s)
228 return bool(s and '\0' in s)
229
229
230 def increasingchunks(source, min=1024, max=65536):
230 def increasingchunks(source, min=1024, max=65536):
231 '''return no less than min bytes per chunk while data remains,
231 '''return no less than min bytes per chunk while data remains,
232 doubling min after each chunk until it reaches max'''
232 doubling min after each chunk until it reaches max'''
233 def log2(x):
233 def log2(x):
234 if not x:
234 if not x:
235 return 0
235 return 0
236 i = 0
236 i = 0
237 while x:
237 while x:
238 x >>= 1
238 x >>= 1
239 i += 1
239 i += 1
240 return i - 1
240 return i - 1
241
241
242 buf = []
242 buf = []
243 blen = 0
243 blen = 0
244 for chunk in source:
244 for chunk in source:
245 buf.append(chunk)
245 buf.append(chunk)
246 blen += len(chunk)
246 blen += len(chunk)
247 if blen >= min:
247 if blen >= min:
248 if min < max:
248 if min < max:
249 min = min << 1
249 min = min << 1
250 nmin = 1 << log2(blen)
250 nmin = 1 << log2(blen)
251 if nmin > min:
251 if nmin > min:
252 min = nmin
252 min = nmin
253 if min > max:
253 if min > max:
254 min = max
254 min = max
255 yield ''.join(buf)
255 yield ''.join(buf)
256 blen = 0
256 blen = 0
257 buf = []
257 buf = []
258 if buf:
258 if buf:
259 yield ''.join(buf)
259 yield ''.join(buf)
260
260
261 Abort = error.Abort
261 Abort = error.Abort
262
262
263 def always(fn):
263 def always(fn):
264 return True
264 return True
265
265
266 def never(fn):
266 def never(fn):
267 return False
267 return False
268
268
269 def pathto(root, n1, n2):
269 def pathto(root, n1, n2):
270 '''return the relative path from one place to another.
270 '''return the relative path from one place to another.
271 root should use os.sep to separate directories
271 root should use os.sep to separate directories
272 n1 should use os.sep to separate directories
272 n1 should use os.sep to separate directories
273 n2 should use "/" to separate directories
273 n2 should use "/" to separate directories
274 returns an os.sep-separated path.
274 returns an os.sep-separated path.
275
275
276 If n1 is a relative path, it's assumed it's
276 If n1 is a relative path, it's assumed it's
277 relative to root.
277 relative to root.
278 n2 should always be relative to root.
278 n2 should always be relative to root.
279 '''
279 '''
280 if not n1:
280 if not n1:
281 return localpath(n2)
281 return localpath(n2)
282 if os.path.isabs(n1):
282 if os.path.isabs(n1):
283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
284 return os.path.join(root, localpath(n2))
284 return os.path.join(root, localpath(n2))
285 n2 = '/'.join((pconvert(root), n2))
285 n2 = '/'.join((pconvert(root), n2))
286 a, b = splitpath(n1), n2.split('/')
286 a, b = splitpath(n1), n2.split('/')
287 a.reverse()
287 a.reverse()
288 b.reverse()
288 b.reverse()
289 while a and b and a[-1] == b[-1]:
289 while a and b and a[-1] == b[-1]:
290 a.pop()
290 a.pop()
291 b.pop()
291 b.pop()
292 b.reverse()
292 b.reverse()
293 return os.sep.join((['..'] * len(a)) + b) or '.'
293 return os.sep.join((['..'] * len(a)) + b) or '.'
294
294
295 def canonpath(root, cwd, myname, auditor=None):
295 def canonpath(root, cwd, myname, auditor=None):
296 """return the canonical path of myname, given cwd and root"""
296 """return the canonical path of myname, given cwd and root"""
297 if endswithsep(root):
297 if endswithsep(root):
298 rootsep = root
298 rootsep = root
299 else:
299 else:
300 rootsep = root + os.sep
300 rootsep = root + os.sep
301 name = myname
301 name = myname
302 if not os.path.isabs(name):
302 if not os.path.isabs(name):
303 name = os.path.join(root, cwd, name)
303 name = os.path.join(root, cwd, name)
304 name = os.path.normpath(name)
304 name = os.path.normpath(name)
305 if auditor is None:
305 if auditor is None:
306 auditor = path_auditor(root)
306 auditor = path_auditor(root)
307 if name != rootsep and name.startswith(rootsep):
307 if name != rootsep and name.startswith(rootsep):
308 name = name[len(rootsep):]
308 name = name[len(rootsep):]
309 auditor(name)
309 auditor(name)
310 return pconvert(name)
310 return pconvert(name)
311 elif name == root:
311 elif name == root:
312 return ''
312 return ''
313 else:
313 else:
314 # Determine whether `name' is in the hierarchy at or beneath `root',
314 # Determine whether `name' is in the hierarchy at or beneath `root',
315 # by iterating name=dirname(name) until that causes no change (can't
315 # by iterating name=dirname(name) until that causes no change (can't
316 # check name == '/', because that doesn't work on windows). For each
316 # check name == '/', because that doesn't work on windows). For each
317 # `name', compare dev/inode numbers. If they match, the list `rel'
317 # `name', compare dev/inode numbers. If they match, the list `rel'
318 # holds the reversed list of components making up the relative file
318 # holds the reversed list of components making up the relative file
319 # name we want.
319 # name we want.
320 root_st = os.stat(root)
320 root_st = os.stat(root)
321 rel = []
321 rel = []
322 while True:
322 while True:
323 try:
323 try:
324 name_st = os.stat(name)
324 name_st = os.stat(name)
325 except OSError:
325 except OSError:
326 break
326 break
327 if samestat(name_st, root_st):
327 if samestat(name_st, root_st):
328 if not rel:
328 if not rel:
329 # name was actually the same as root (maybe a symlink)
329 # name was actually the same as root (maybe a symlink)
330 return ''
330 return ''
331 rel.reverse()
331 rel.reverse()
332 name = os.path.join(*rel)
332 name = os.path.join(*rel)
333 auditor(name)
333 auditor(name)
334 return pconvert(name)
334 return pconvert(name)
335 dirname, basename = os.path.split(name)
335 dirname, basename = os.path.split(name)
336 rel.append(basename)
336 rel.append(basename)
337 if dirname == name:
337 if dirname == name:
338 break
338 break
339 name = dirname
339 name = dirname
340
340
341 raise Abort('%s not under root' % myname)
341 raise Abort('%s not under root' % myname)
342
342
343 _hgexecutable = None
343 _hgexecutable = None
344
344
345 def main_is_frozen():
345 def main_is_frozen():
346 """return True if we are a frozen executable.
346 """return True if we are a frozen executable.
347
347
348 The code supports py2exe (most common, Windows only) and tools/freeze
348 The code supports py2exe (most common, Windows only) and tools/freeze
349 (portable, not much used).
349 (portable, not much used).
350 """
350 """
351 return (hasattr(sys, "frozen") or # new py2exe
351 return (hasattr(sys, "frozen") or # new py2exe
352 hasattr(sys, "importers") or # old py2exe
352 hasattr(sys, "importers") or # old py2exe
353 imp.is_frozen("__main__")) # tools/freeze
353 imp.is_frozen("__main__")) # tools/freeze
354
354
355 def hgexecutable():
355 def hgexecutable():
356 """return location of the 'hg' executable.
356 """return location of the 'hg' executable.
357
357
358 Defaults to $HG or 'hg' in the search path.
358 Defaults to $HG or 'hg' in the search path.
359 """
359 """
360 if _hgexecutable is None:
360 if _hgexecutable is None:
361 hg = os.environ.get('HG')
361 hg = os.environ.get('HG')
362 if hg:
362 if hg:
363 set_hgexecutable(hg)
363 set_hgexecutable(hg)
364 elif main_is_frozen():
364 elif main_is_frozen():
365 set_hgexecutable(sys.executable)
365 set_hgexecutable(sys.executable)
366 else:
366 else:
367 exe = find_exe('hg') or os.path.basename(sys.argv[0])
367 exe = find_exe('hg') or os.path.basename(sys.argv[0])
368 set_hgexecutable(exe)
368 set_hgexecutable(exe)
369 return _hgexecutable
369 return _hgexecutable
370
370
371 def set_hgexecutable(path):
371 def set_hgexecutable(path):
372 """set location of the 'hg' executable"""
372 """set location of the 'hg' executable"""
373 global _hgexecutable
373 global _hgexecutable
374 _hgexecutable = path
374 _hgexecutable = path
375
375
376 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
376 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
377 '''enhanced shell command execution.
377 '''enhanced shell command execution.
378 run with environment maybe modified, maybe in different dir.
378 run with environment maybe modified, maybe in different dir.
379
379
380 if command fails and onerr is None, return status. if ui object,
380 if command fails and onerr is None, return status. if ui object,
381 print error message and return status, else raise onerr object as
381 print error message and return status, else raise onerr object as
382 exception.
382 exception.
383
383
384 if out is specified, it is assumed to be a file-like object that has a
384 if out is specified, it is assumed to be a file-like object that has a
385 write() method. stdout and stderr will be redirected to out.'''
385 write() method. stdout and stderr will be redirected to out.'''
386 def py2shell(val):
386 def py2shell(val):
387 'convert python object into string that is useful to shell'
387 'convert python object into string that is useful to shell'
388 if val is None or val is False:
388 if val is None or val is False:
389 return '0'
389 return '0'
390 if val is True:
390 if val is True:
391 return '1'
391 return '1'
392 return str(val)
392 return str(val)
393 origcmd = cmd
393 origcmd = cmd
394 cmd = quotecommand(cmd)
394 cmd = quotecommand(cmd)
395 env = dict(os.environ)
395 env = dict(os.environ)
396 env.update((k, py2shell(v)) for k, v in environ.iteritems())
396 env.update((k, py2shell(v)) for k, v in environ.iteritems())
397 env['HG'] = hgexecutable()
397 env['HG'] = hgexecutable()
398 if out is None:
398 if out is None:
399 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
399 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
400 env=env, cwd=cwd)
400 env=env, cwd=cwd)
401 else:
401 else:
402 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
402 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
403 env=env, cwd=cwd, stdout=subprocess.PIPE,
403 env=env, cwd=cwd, stdout=subprocess.PIPE,
404 stderr=subprocess.STDOUT)
404 stderr=subprocess.STDOUT)
405 for line in proc.stdout:
405 for line in proc.stdout:
406 out.write(line)
406 out.write(line)
407 proc.wait()
407 proc.wait()
408 rc = proc.returncode
408 rc = proc.returncode
409 if sys.platform == 'OpenVMS' and rc & 1:
409 if sys.platform == 'OpenVMS' and rc & 1:
410 rc = 0
410 rc = 0
411 if rc and onerr:
411 if rc and onerr:
412 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
412 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
413 explain_exit(rc)[0])
413 explain_exit(rc)[0])
414 if errprefix:
414 if errprefix:
415 errmsg = '%s: %s' % (errprefix, errmsg)
415 errmsg = '%s: %s' % (errprefix, errmsg)
416 try:
416 try:
417 onerr.warn(errmsg + '\n')
417 onerr.warn(errmsg + '\n')
418 except AttributeError:
418 except AttributeError:
419 raise onerr(errmsg)
419 raise onerr(errmsg)
420 return rc
420 return rc
421
421
422 def checksignature(func):
422 def checksignature(func):
423 '''wrap a function with code to check for calling errors'''
423 '''wrap a function with code to check for calling errors'''
424 def check(*args, **kwargs):
424 def check(*args, **kwargs):
425 try:
425 try:
426 return func(*args, **kwargs)
426 return func(*args, **kwargs)
427 except TypeError:
427 except TypeError:
428 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
428 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
429 raise error.SignatureError
429 raise error.SignatureError
430 raise
430 raise
431
431
432 return check
432 return check
433
433
434 def unlinkpath(f):
434 def unlinkpath(f):
435 """unlink and remove the directory if it is empty"""
435 """unlink and remove the directory if it is empty"""
436 os.unlink(f)
436 os.unlink(f)
437 # try removing directories that might now be empty
437 # try removing directories that might now be empty
438 try:
438 try:
439 os.removedirs(os.path.dirname(f))
439 os.removedirs(os.path.dirname(f))
440 except OSError:
440 except OSError:
441 pass
441 pass
442
442
443 def copyfile(src, dest):
443 def copyfile(src, dest):
444 "copy a file, preserving mode and atime/mtime"
444 "copy a file, preserving mode and atime/mtime"
445 if os.path.islink(src):
445 if os.path.islink(src):
446 try:
446 try:
447 os.unlink(dest)
447 os.unlink(dest)
448 except:
448 except:
449 pass
449 pass
450 os.symlink(os.readlink(src), dest)
450 os.symlink(os.readlink(src), dest)
451 else:
451 else:
452 try:
452 try:
453 shutil.copyfile(src, dest)
453 shutil.copyfile(src, dest)
454 shutil.copymode(src, dest)
454 shutil.copymode(src, dest)
455 except shutil.Error, inst:
455 except shutil.Error, inst:
456 raise Abort(str(inst))
456 raise Abort(str(inst))
457
457
458 def copyfiles(src, dst, hardlink=None):
458 def copyfiles(src, dst, hardlink=None):
459 """Copy a directory tree using hardlinks if possible"""
459 """Copy a directory tree using hardlinks if possible"""
460
460
461 if hardlink is None:
461 if hardlink is None:
462 hardlink = (os.stat(src).st_dev ==
462 hardlink = (os.stat(src).st_dev ==
463 os.stat(os.path.dirname(dst)).st_dev)
463 os.stat(os.path.dirname(dst)).st_dev)
464
464
465 num = 0
465 num = 0
466 if os.path.isdir(src):
466 if os.path.isdir(src):
467 os.mkdir(dst)
467 os.mkdir(dst)
468 for name, kind in osutil.listdir(src):
468 for name, kind in osutil.listdir(src):
469 srcname = os.path.join(src, name)
469 srcname = os.path.join(src, name)
470 dstname = os.path.join(dst, name)
470 dstname = os.path.join(dst, name)
471 hardlink, n = copyfiles(srcname, dstname, hardlink)
471 hardlink, n = copyfiles(srcname, dstname, hardlink)
472 num += n
472 num += n
473 else:
473 else:
474 if hardlink:
474 if hardlink:
475 try:
475 try:
476 os_link(src, dst)
476 os_link(src, dst)
477 except (IOError, OSError):
477 except (IOError, OSError):
478 hardlink = False
478 hardlink = False
479 shutil.copy(src, dst)
479 shutil.copy(src, dst)
480 else:
480 else:
481 shutil.copy(src, dst)
481 shutil.copy(src, dst)
482 num += 1
482 num += 1
483
483
484 return hardlink, num
484 return hardlink, num
485
485
486 class path_auditor(object):
486 class path_auditor(object):
487 '''ensure that a filesystem path contains no banned components.
487 '''ensure that a filesystem path contains no banned components.
488 the following properties of a path are checked:
488 the following properties of a path are checked:
489
489
490 - ends with a directory separator
490 - ends with a directory separator
491 - under top-level .hg
491 - under top-level .hg
492 - starts at the root of a windows drive
492 - starts at the root of a windows drive
493 - contains ".."
493 - contains ".."
494 - traverses a symlink (e.g. a/symlink_here/b)
494 - traverses a symlink (e.g. a/symlink_here/b)
495 - inside a nested repository (a callback can be used to approve
495 - inside a nested repository (a callback can be used to approve
496 some nested repositories, e.g., subrepositories)
496 some nested repositories, e.g., subrepositories)
497 '''
497 '''
498
498
499 def __init__(self, root, callback=None):
499 def __init__(self, root, callback=None):
500 self.audited = set()
500 self.audited = set()
501 self.auditeddir = set()
501 self.auditeddir = set()
502 self.root = root
502 self.root = root
503 self.callback = callback
503 self.callback = callback
504
504
505 def __call__(self, path):
505 def __call__(self, path):
506 if path in self.audited:
506 if path in self.audited:
507 return
507 return
508 # AIX ignores "/" at end of path, others raise EISDIR.
508 # AIX ignores "/" at end of path, others raise EISDIR.
509 if endswithsep(path):
509 if endswithsep(path):
510 raise Abort(_("path ends in directory separator: %s") % path)
510 raise Abort(_("path ends in directory separator: %s") % path)
511 normpath = os.path.normcase(path)
511 normpath = os.path.normcase(path)
512 parts = splitpath(normpath)
512 parts = splitpath(normpath)
513 if (os.path.splitdrive(path)[0]
513 if (os.path.splitdrive(path)[0]
514 or parts[0].lower() in ('.hg', '.hg.', '')
514 or parts[0].lower() in ('.hg', '.hg.', '')
515 or os.pardir in parts):
515 or os.pardir in parts):
516 raise Abort(_("path contains illegal component: %s") % path)
516 raise Abort(_("path contains illegal component: %s") % path)
517 if '.hg' in path.lower():
517 if '.hg' in path.lower():
518 lparts = [p.lower() for p in parts]
518 lparts = [p.lower() for p in parts]
519 for p in '.hg', '.hg.':
519 for p in '.hg', '.hg.':
520 if p in lparts[1:]:
520 if p in lparts[1:]:
521 pos = lparts.index(p)
521 pos = lparts.index(p)
522 base = os.path.join(*parts[:pos])
522 base = os.path.join(*parts[:pos])
523 raise Abort(_('path %r is inside repo %r') % (path, base))
523 raise Abort(_('path %r is inside repo %r') % (path, base))
524 def check(prefix):
524 def check(prefix):
525 curpath = os.path.join(self.root, prefix)
525 curpath = os.path.join(self.root, prefix)
526 try:
526 try:
527 st = os.lstat(curpath)
527 st = os.lstat(curpath)
528 except OSError, err:
528 except OSError, err:
529 # EINVAL can be raised as invalid path syntax under win32.
529 # EINVAL can be raised as invalid path syntax under win32.
530 # They must be ignored for patterns can be checked too.
530 # They must be ignored for patterns can be checked too.
531 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
531 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
532 raise
532 raise
533 else:
533 else:
534 if stat.S_ISLNK(st.st_mode):
534 if stat.S_ISLNK(st.st_mode):
535 raise Abort(_('path %r traverses symbolic link %r') %
535 raise Abort(_('path %r traverses symbolic link %r') %
536 (path, prefix))
536 (path, prefix))
537 elif (stat.S_ISDIR(st.st_mode) and
537 elif (stat.S_ISDIR(st.st_mode) and
538 os.path.isdir(os.path.join(curpath, '.hg'))):
538 os.path.isdir(os.path.join(curpath, '.hg'))):
539 if not self.callback or not self.callback(curpath):
539 if not self.callback or not self.callback(curpath):
540 raise Abort(_('path %r is inside repo %r') %
540 raise Abort(_('path %r is inside repo %r') %
541 (path, prefix))
541 (path, prefix))
542 parts.pop()
542 parts.pop()
543 prefixes = []
543 prefixes = []
544 while parts:
544 while parts:
545 prefix = os.sep.join(parts)
545 prefix = os.sep.join(parts)
546 if prefix in self.auditeddir:
546 if prefix in self.auditeddir:
547 break
547 break
548 check(prefix)
548 check(prefix)
549 prefixes.append(prefix)
549 prefixes.append(prefix)
550 parts.pop()
550 parts.pop()
551
551
552 self.audited.add(path)
552 self.audited.add(path)
553 # only add prefixes to the cache after checking everything: we don't
553 # only add prefixes to the cache after checking everything: we don't
554 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
554 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
555 self.auditeddir.update(prefixes)
555 self.auditeddir.update(prefixes)
556
556
557 def nlinks(pathname):
558 """Return number of hardlinks for the given file."""
559 return os.lstat(pathname).st_nlink
560
561 if hasattr(os, 'link'):
562 os_link = os.link
563 else:
564 def os_link(src, dst):
565 raise OSError(0, _("Hardlinks not supported"))
566
567 def lookup_reg(key, name=None, scope=None):
557 def lookup_reg(key, name=None, scope=None):
568 return None
558 return None
569
559
570 def hidewindow():
560 def hidewindow():
571 """Hide current shell window.
561 """Hide current shell window.
572
562
573 Used to hide the window opened when starting asynchronous
563 Used to hide the window opened when starting asynchronous
574 child process under Windows, unneeded on other systems.
564 child process under Windows, unneeded on other systems.
575 """
565 """
576 pass
566 pass
577
567
578 if os.name == 'nt':
568 if os.name == 'nt':
579 from windows import *
569 from windows import *
580 else:
570 else:
581 from posix import *
571 from posix import *
582
572
583 def makelock(info, pathname):
573 def makelock(info, pathname):
584 try:
574 try:
585 return os.symlink(info, pathname)
575 return os.symlink(info, pathname)
586 except OSError, why:
576 except OSError, why:
587 if why.errno == errno.EEXIST:
577 if why.errno == errno.EEXIST:
588 raise
578 raise
589 except AttributeError: # no symlink in os
579 except AttributeError: # no symlink in os
590 pass
580 pass
591
581
592 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
582 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
593 os.write(ld, info)
583 os.write(ld, info)
594 os.close(ld)
584 os.close(ld)
595
585
596 def readlock(pathname):
586 def readlock(pathname):
597 try:
587 try:
598 return os.readlink(pathname)
588 return os.readlink(pathname)
599 except OSError, why:
589 except OSError, why:
600 if why.errno not in (errno.EINVAL, errno.ENOSYS):
590 if why.errno not in (errno.EINVAL, errno.ENOSYS):
601 raise
591 raise
602 except AttributeError: # no symlink in os
592 except AttributeError: # no symlink in os
603 pass
593 pass
604 return posixfile(pathname).read()
594 return posixfile(pathname).read()
605
595
606 def fstat(fp):
596 def fstat(fp):
607 '''stat file object that may not have fileno method.'''
597 '''stat file object that may not have fileno method.'''
608 try:
598 try:
609 return os.fstat(fp.fileno())
599 return os.fstat(fp.fileno())
610 except AttributeError:
600 except AttributeError:
611 return os.stat(fp.name)
601 return os.stat(fp.name)
612
602
613 # File system features
603 # File system features
614
604
615 def checkcase(path):
605 def checkcase(path):
616 """
606 """
617 Check whether the given path is on a case-sensitive filesystem
607 Check whether the given path is on a case-sensitive filesystem
618
608
619 Requires a path (like /foo/.hg) ending with a foldable final
609 Requires a path (like /foo/.hg) ending with a foldable final
620 directory component.
610 directory component.
621 """
611 """
622 s1 = os.stat(path)
612 s1 = os.stat(path)
623 d, b = os.path.split(path)
613 d, b = os.path.split(path)
624 p2 = os.path.join(d, b.upper())
614 p2 = os.path.join(d, b.upper())
625 if path == p2:
615 if path == p2:
626 p2 = os.path.join(d, b.lower())
616 p2 = os.path.join(d, b.lower())
627 try:
617 try:
628 s2 = os.stat(p2)
618 s2 = os.stat(p2)
629 if s2 == s1:
619 if s2 == s1:
630 return False
620 return False
631 return True
621 return True
632 except:
622 except:
633 return True
623 return True
634
624
635 _fspathcache = {}
625 _fspathcache = {}
636 def fspath(name, root):
626 def fspath(name, root):
637 '''Get name in the case stored in the filesystem
627 '''Get name in the case stored in the filesystem
638
628
639 The name is either relative to root, or it is an absolute path starting
629 The name is either relative to root, or it is an absolute path starting
640 with root. Note that this function is unnecessary, and should not be
630 with root. Note that this function is unnecessary, and should not be
641 called, for case-sensitive filesystems (simply because it's expensive).
631 called, for case-sensitive filesystems (simply because it's expensive).
642 '''
632 '''
643 # If name is absolute, make it relative
633 # If name is absolute, make it relative
644 if name.lower().startswith(root.lower()):
634 if name.lower().startswith(root.lower()):
645 l = len(root)
635 l = len(root)
646 if name[l] == os.sep or name[l] == os.altsep:
636 if name[l] == os.sep or name[l] == os.altsep:
647 l = l + 1
637 l = l + 1
648 name = name[l:]
638 name = name[l:]
649
639
650 if not os.path.lexists(os.path.join(root, name)):
640 if not os.path.lexists(os.path.join(root, name)):
651 return None
641 return None
652
642
653 seps = os.sep
643 seps = os.sep
654 if os.altsep:
644 if os.altsep:
655 seps = seps + os.altsep
645 seps = seps + os.altsep
656 # Protect backslashes. This gets silly very quickly.
646 # Protect backslashes. This gets silly very quickly.
657 seps.replace('\\','\\\\')
647 seps.replace('\\','\\\\')
658 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
648 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
659 dir = os.path.normcase(os.path.normpath(root))
649 dir = os.path.normcase(os.path.normpath(root))
660 result = []
650 result = []
661 for part, sep in pattern.findall(name):
651 for part, sep in pattern.findall(name):
662 if sep:
652 if sep:
663 result.append(sep)
653 result.append(sep)
664 continue
654 continue
665
655
666 if dir not in _fspathcache:
656 if dir not in _fspathcache:
667 _fspathcache[dir] = os.listdir(dir)
657 _fspathcache[dir] = os.listdir(dir)
668 contents = _fspathcache[dir]
658 contents = _fspathcache[dir]
669
659
670 lpart = part.lower()
660 lpart = part.lower()
671 lenp = len(part)
661 lenp = len(part)
672 for n in contents:
662 for n in contents:
673 if lenp == len(n) and n.lower() == lpart:
663 if lenp == len(n) and n.lower() == lpart:
674 result.append(n)
664 result.append(n)
675 break
665 break
676 else:
666 else:
677 # Cannot happen, as the file exists!
667 # Cannot happen, as the file exists!
678 result.append(part)
668 result.append(part)
679 dir = os.path.join(dir, lpart)
669 dir = os.path.join(dir, lpart)
680
670
681 return ''.join(result)
671 return ''.join(result)
682
672
683 def checkexec(path):
673 def checkexec(path):
684 """
674 """
685 Check whether the given path is on a filesystem with UNIX-like exec flags
675 Check whether the given path is on a filesystem with UNIX-like exec flags
686
676
687 Requires a directory (like /foo/.hg)
677 Requires a directory (like /foo/.hg)
688 """
678 """
689
679
690 # VFAT on some Linux versions can flip mode but it doesn't persist
680 # VFAT on some Linux versions can flip mode but it doesn't persist
691 # a FS remount. Frequently we can detect it if files are created
681 # a FS remount. Frequently we can detect it if files are created
692 # with exec bit on.
682 # with exec bit on.
693
683
694 try:
684 try:
695 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
685 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
696 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
686 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
697 try:
687 try:
698 os.close(fh)
688 os.close(fh)
699 m = os.stat(fn).st_mode & 0777
689 m = os.stat(fn).st_mode & 0777
700 new_file_has_exec = m & EXECFLAGS
690 new_file_has_exec = m & EXECFLAGS
701 os.chmod(fn, m ^ EXECFLAGS)
691 os.chmod(fn, m ^ EXECFLAGS)
702 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
692 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
703 finally:
693 finally:
704 os.unlink(fn)
694 os.unlink(fn)
705 except (IOError, OSError):
695 except (IOError, OSError):
706 # we don't care, the user probably won't be able to commit anyway
696 # we don't care, the user probably won't be able to commit anyway
707 return False
697 return False
708 return not (new_file_has_exec or exec_flags_cannot_flip)
698 return not (new_file_has_exec or exec_flags_cannot_flip)
709
699
710 def checklink(path):
700 def checklink(path):
711 """check whether the given path is on a symlink-capable filesystem"""
701 """check whether the given path is on a symlink-capable filesystem"""
712 # mktemp is not racy because symlink creation will fail if the
702 # mktemp is not racy because symlink creation will fail if the
713 # file already exists
703 # file already exists
714 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
704 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
715 try:
705 try:
716 os.symlink(".", name)
706 os.symlink(".", name)
717 os.unlink(name)
707 os.unlink(name)
718 return True
708 return True
719 except (OSError, AttributeError):
709 except (OSError, AttributeError):
720 return False
710 return False
721
711
722 def checknlink(testfile):
712 def checknlink(testfile):
723 '''check whether hardlink count reporting works properly'''
713 '''check whether hardlink count reporting works properly'''
724
714
725 # testfile may be open, so we need a separate file for checking to
715 # testfile may be open, so we need a separate file for checking to
726 # work around issue2543 (or testfile may get lost on Samba shares)
716 # work around issue2543 (or testfile may get lost on Samba shares)
727 f1 = testfile + ".hgtmp1"
717 f1 = testfile + ".hgtmp1"
728 if os.path.lexists(f1):
718 if os.path.lexists(f1):
729 return False
719 return False
730 try:
720 try:
731 posixfile(f1, 'w').close()
721 posixfile(f1, 'w').close()
732 except IOError:
722 except IOError:
733 return False
723 return False
734
724
735 f2 = testfile + ".hgtmp2"
725 f2 = testfile + ".hgtmp2"
736 fd = None
726 fd = None
737 try:
727 try:
738 try:
728 try:
739 os_link(f1, f2)
729 os_link(f1, f2)
740 except OSError:
730 except OSError:
741 return False
731 return False
742
732
743 # nlinks() may behave differently for files on Windows shares if
733 # nlinks() may behave differently for files on Windows shares if
744 # the file is open.
734 # the file is open.
745 fd = posixfile(f2)
735 fd = posixfile(f2)
746 return nlinks(f2) > 1
736 return nlinks(f2) > 1
747 finally:
737 finally:
748 if fd is not None:
738 if fd is not None:
749 fd.close()
739 fd.close()
750 for f in (f1, f2):
740 for f in (f1, f2):
751 try:
741 try:
752 os.unlink(f)
742 os.unlink(f)
753 except OSError:
743 except OSError:
754 pass
744 pass
755
745
756 return False
746 return False
757
747
758 def endswithsep(path):
748 def endswithsep(path):
759 '''Check path ends with os.sep or os.altsep.'''
749 '''Check path ends with os.sep or os.altsep.'''
760 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
750 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
761
751
762 def splitpath(path):
752 def splitpath(path):
763 '''Split path by os.sep.
753 '''Split path by os.sep.
764 Note that this function does not use os.altsep because this is
754 Note that this function does not use os.altsep because this is
765 an alternative of simple "xxx.split(os.sep)".
755 an alternative of simple "xxx.split(os.sep)".
766 It is recommended to use os.path.normpath() before using this
756 It is recommended to use os.path.normpath() before using this
767 function if need.'''
757 function if need.'''
768 return path.split(os.sep)
758 return path.split(os.sep)
769
759
770 def gui():
760 def gui():
771 '''Are we running in a GUI?'''
761 '''Are we running in a GUI?'''
772 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
762 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
773
763
774 def mktempcopy(name, emptyok=False, createmode=None):
764 def mktempcopy(name, emptyok=False, createmode=None):
775 """Create a temporary file with the same contents from name
765 """Create a temporary file with the same contents from name
776
766
777 The permission bits are copied from the original file.
767 The permission bits are copied from the original file.
778
768
779 If the temporary file is going to be truncated immediately, you
769 If the temporary file is going to be truncated immediately, you
780 can use emptyok=True as an optimization.
770 can use emptyok=True as an optimization.
781
771
782 Returns the name of the temporary file.
772 Returns the name of the temporary file.
783 """
773 """
784 d, fn = os.path.split(name)
774 d, fn = os.path.split(name)
785 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
775 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
786 os.close(fd)
776 os.close(fd)
787 # Temporary files are created with mode 0600, which is usually not
777 # Temporary files are created with mode 0600, which is usually not
788 # what we want. If the original file already exists, just copy
778 # what we want. If the original file already exists, just copy
789 # its mode. Otherwise, manually obey umask.
779 # its mode. Otherwise, manually obey umask.
790 try:
780 try:
791 st_mode = os.lstat(name).st_mode & 0777
781 st_mode = os.lstat(name).st_mode & 0777
792 except OSError, inst:
782 except OSError, inst:
793 if inst.errno != errno.ENOENT:
783 if inst.errno != errno.ENOENT:
794 raise
784 raise
795 st_mode = createmode
785 st_mode = createmode
796 if st_mode is None:
786 if st_mode is None:
797 st_mode = ~umask
787 st_mode = ~umask
798 st_mode &= 0666
788 st_mode &= 0666
799 os.chmod(temp, st_mode)
789 os.chmod(temp, st_mode)
800 if emptyok:
790 if emptyok:
801 return temp
791 return temp
802 try:
792 try:
803 try:
793 try:
804 ifp = posixfile(name, "rb")
794 ifp = posixfile(name, "rb")
805 except IOError, inst:
795 except IOError, inst:
806 if inst.errno == errno.ENOENT:
796 if inst.errno == errno.ENOENT:
807 return temp
797 return temp
808 if not getattr(inst, 'filename', None):
798 if not getattr(inst, 'filename', None):
809 inst.filename = name
799 inst.filename = name
810 raise
800 raise
811 ofp = posixfile(temp, "wb")
801 ofp = posixfile(temp, "wb")
812 for chunk in filechunkiter(ifp):
802 for chunk in filechunkiter(ifp):
813 ofp.write(chunk)
803 ofp.write(chunk)
814 ifp.close()
804 ifp.close()
815 ofp.close()
805 ofp.close()
816 except:
806 except:
817 try: os.unlink(temp)
807 try: os.unlink(temp)
818 except: pass
808 except: pass
819 raise
809 raise
820 return temp
810 return temp
821
811
822 class atomictempfile(object):
812 class atomictempfile(object):
823 """file-like object that atomically updates a file
813 """file-like object that atomically updates a file
824
814
825 All writes will be redirected to a temporary copy of the original
815 All writes will be redirected to a temporary copy of the original
826 file. When rename is called, the copy is renamed to the original
816 file. When rename is called, the copy is renamed to the original
827 name, making the changes visible.
817 name, making the changes visible.
828 """
818 """
829 def __init__(self, name, mode='w+b', createmode=None):
819 def __init__(self, name, mode='w+b', createmode=None):
830 self.__name = name
820 self.__name = name
831 self._fp = None
821 self._fp = None
832 self.temp = mktempcopy(name, emptyok=('w' in mode),
822 self.temp = mktempcopy(name, emptyok=('w' in mode),
833 createmode=createmode)
823 createmode=createmode)
834 self._fp = posixfile(self.temp, mode)
824 self._fp = posixfile(self.temp, mode)
835
825
836 def __getattr__(self, name):
826 def __getattr__(self, name):
837 return getattr(self._fp, name)
827 return getattr(self._fp, name)
838
828
839 def rename(self):
829 def rename(self):
840 if not self._fp.closed:
830 if not self._fp.closed:
841 self._fp.close()
831 self._fp.close()
842 rename(self.temp, localpath(self.__name))
832 rename(self.temp, localpath(self.__name))
843
833
844 def close(self):
834 def close(self):
845 if not self._fp:
835 if not self._fp:
846 return
836 return
847 if not self._fp.closed:
837 if not self._fp.closed:
848 try:
838 try:
849 os.unlink(self.temp)
839 os.unlink(self.temp)
850 except: pass
840 except: pass
851 self._fp.close()
841 self._fp.close()
852
842
853 def __del__(self):
843 def __del__(self):
854 self.close()
844 self.close()
855
845
856 def makedirs(name, mode=None):
846 def makedirs(name, mode=None):
857 """recursive directory creation with parent mode inheritance"""
847 """recursive directory creation with parent mode inheritance"""
858 parent = os.path.abspath(os.path.dirname(name))
848 parent = os.path.abspath(os.path.dirname(name))
859 try:
849 try:
860 os.mkdir(name)
850 os.mkdir(name)
861 if mode is not None:
851 if mode is not None:
862 os.chmod(name, mode)
852 os.chmod(name, mode)
863 return
853 return
864 except OSError, err:
854 except OSError, err:
865 if err.errno == errno.EEXIST:
855 if err.errno == errno.EEXIST:
866 return
856 return
867 if not name or parent == name or err.errno != errno.ENOENT:
857 if not name or parent == name or err.errno != errno.ENOENT:
868 raise
858 raise
869 makedirs(parent, mode)
859 makedirs(parent, mode)
870 makedirs(name, mode)
860 makedirs(name, mode)
871
861
872 class opener(object):
862 class opener(object):
873 """Open files relative to a base directory
863 """Open files relative to a base directory
874
864
875 This class is used to hide the details of COW semantics and
865 This class is used to hide the details of COW semantics and
876 remote file access from higher level code.
866 remote file access from higher level code.
877 """
867 """
878 def __init__(self, base, audit=True):
868 def __init__(self, base, audit=True):
879 self.base = base
869 self.base = base
880 if audit:
870 if audit:
881 self.auditor = path_auditor(base)
871 self.auditor = path_auditor(base)
882 else:
872 else:
883 self.auditor = always
873 self.auditor = always
884 self.createmode = None
874 self.createmode = None
885 self._trustnlink = None
875 self._trustnlink = None
886
876
887 @propertycache
877 @propertycache
888 def _can_symlink(self):
878 def _can_symlink(self):
889 return checklink(self.base)
879 return checklink(self.base)
890
880
891 def _fixfilemode(self, name):
881 def _fixfilemode(self, name):
892 if self.createmode is None:
882 if self.createmode is None:
893 return
883 return
894 os.chmod(name, self.createmode & 0666)
884 os.chmod(name, self.createmode & 0666)
895
885
896 def __call__(self, path, mode="r", text=False, atomictemp=False):
886 def __call__(self, path, mode="r", text=False, atomictemp=False):
897 self.auditor(path)
887 self.auditor(path)
898 f = os.path.join(self.base, path)
888 f = os.path.join(self.base, path)
899
889
900 if not text and "b" not in mode:
890 if not text and "b" not in mode:
901 mode += "b" # for that other OS
891 mode += "b" # for that other OS
902
892
903 nlink = -1
893 nlink = -1
904 dirname, basename = os.path.split(f)
894 dirname, basename = os.path.split(f)
905 # If basename is empty, then the path is malformed because it points
895 # If basename is empty, then the path is malformed because it points
906 # to a directory. Let the posixfile() call below raise IOError.
896 # to a directory. Let the posixfile() call below raise IOError.
907 if basename and mode not in ('r', 'rb'):
897 if basename and mode not in ('r', 'rb'):
908 if atomictemp:
898 if atomictemp:
909 if not os.path.isdir(dirname):
899 if not os.path.isdir(dirname):
910 makedirs(dirname, self.createmode)
900 makedirs(dirname, self.createmode)
911 return atomictempfile(f, mode, self.createmode)
901 return atomictempfile(f, mode, self.createmode)
912 try:
902 try:
913 if 'w' in mode:
903 if 'w' in mode:
914 unlink(f)
904 unlink(f)
915 nlink = 0
905 nlink = 0
916 else:
906 else:
917 # nlinks() may behave differently for files on Windows
907 # nlinks() may behave differently for files on Windows
918 # shares if the file is open.
908 # shares if the file is open.
919 fd = posixfile(f)
909 fd = posixfile(f)
920 nlink = nlinks(f)
910 nlink = nlinks(f)
921 if nlink < 1:
911 if nlink < 1:
922 nlink = 2 # force mktempcopy (issue1922)
912 nlink = 2 # force mktempcopy (issue1922)
923 fd.close()
913 fd.close()
924 except (OSError, IOError), e:
914 except (OSError, IOError), e:
925 if e.errno != errno.ENOENT:
915 if e.errno != errno.ENOENT:
926 raise
916 raise
927 nlink = 0
917 nlink = 0
928 if not os.path.isdir(dirname):
918 if not os.path.isdir(dirname):
929 makedirs(dirname, self.createmode)
919 makedirs(dirname, self.createmode)
930 if nlink > 0:
920 if nlink > 0:
931 if self._trustnlink is None:
921 if self._trustnlink is None:
932 self._trustnlink = nlink > 1 or checknlink(f)
922 self._trustnlink = nlink > 1 or checknlink(f)
933 if nlink > 1 or not self._trustnlink:
923 if nlink > 1 or not self._trustnlink:
934 rename(mktempcopy(f), f)
924 rename(mktempcopy(f), f)
935 fp = posixfile(f, mode)
925 fp = posixfile(f, mode)
936 if nlink == 0:
926 if nlink == 0:
937 self._fixfilemode(f)
927 self._fixfilemode(f)
938 return fp
928 return fp
939
929
940 def symlink(self, src, dst):
930 def symlink(self, src, dst):
941 self.auditor(dst)
931 self.auditor(dst)
942 linkname = os.path.join(self.base, dst)
932 linkname = os.path.join(self.base, dst)
943 try:
933 try:
944 os.unlink(linkname)
934 os.unlink(linkname)
945 except OSError:
935 except OSError:
946 pass
936 pass
947
937
948 dirname = os.path.dirname(linkname)
938 dirname = os.path.dirname(linkname)
949 if not os.path.exists(dirname):
939 if not os.path.exists(dirname):
950 makedirs(dirname, self.createmode)
940 makedirs(dirname, self.createmode)
951
941
952 if self._can_symlink:
942 if self._can_symlink:
953 try:
943 try:
954 os.symlink(src, linkname)
944 os.symlink(src, linkname)
955 except OSError, err:
945 except OSError, err:
956 raise OSError(err.errno, _('could not symlink to %r: %s') %
946 raise OSError(err.errno, _('could not symlink to %r: %s') %
957 (src, err.strerror), linkname)
947 (src, err.strerror), linkname)
958 else:
948 else:
959 f = self(dst, "w")
949 f = self(dst, "w")
960 f.write(src)
950 f.write(src)
961 f.close()
951 f.close()
962 self._fixfilemode(dst)
952 self._fixfilemode(dst)
963
953
964 class chunkbuffer(object):
954 class chunkbuffer(object):
965 """Allow arbitrary sized chunks of data to be efficiently read from an
955 """Allow arbitrary sized chunks of data to be efficiently read from an
966 iterator over chunks of arbitrary size."""
956 iterator over chunks of arbitrary size."""
967
957
968 def __init__(self, in_iter):
958 def __init__(self, in_iter):
969 """in_iter is the iterator that's iterating over the input chunks.
959 """in_iter is the iterator that's iterating over the input chunks.
970 targetsize is how big a buffer to try to maintain."""
960 targetsize is how big a buffer to try to maintain."""
971 def splitbig(chunks):
961 def splitbig(chunks):
972 for chunk in chunks:
962 for chunk in chunks:
973 if len(chunk) > 2**20:
963 if len(chunk) > 2**20:
974 pos = 0
964 pos = 0
975 while pos < len(chunk):
965 while pos < len(chunk):
976 end = pos + 2 ** 18
966 end = pos + 2 ** 18
977 yield chunk[pos:end]
967 yield chunk[pos:end]
978 pos = end
968 pos = end
979 else:
969 else:
980 yield chunk
970 yield chunk
981 self.iter = splitbig(in_iter)
971 self.iter = splitbig(in_iter)
982 self._queue = []
972 self._queue = []
983
973
984 def read(self, l):
974 def read(self, l):
985 """Read L bytes of data from the iterator of chunks of data.
975 """Read L bytes of data from the iterator of chunks of data.
986 Returns less than L bytes if the iterator runs dry."""
976 Returns less than L bytes if the iterator runs dry."""
987 left = l
977 left = l
988 buf = ''
978 buf = ''
989 queue = self._queue
979 queue = self._queue
990 while left > 0:
980 while left > 0:
991 # refill the queue
981 # refill the queue
992 if not queue:
982 if not queue:
993 target = 2**18
983 target = 2**18
994 for chunk in self.iter:
984 for chunk in self.iter:
995 queue.append(chunk)
985 queue.append(chunk)
996 target -= len(chunk)
986 target -= len(chunk)
997 if target <= 0:
987 if target <= 0:
998 break
988 break
999 if not queue:
989 if not queue:
1000 break
990 break
1001
991
1002 chunk = queue.pop(0)
992 chunk = queue.pop(0)
1003 left -= len(chunk)
993 left -= len(chunk)
1004 if left < 0:
994 if left < 0:
1005 queue.insert(0, chunk[left:])
995 queue.insert(0, chunk[left:])
1006 buf += chunk[:left]
996 buf += chunk[:left]
1007 else:
997 else:
1008 buf += chunk
998 buf += chunk
1009
999
1010 return buf
1000 return buf
1011
1001
1012 def filechunkiter(f, size=65536, limit=None):
1002 def filechunkiter(f, size=65536, limit=None):
1013 """Create a generator that produces the data in the file size
1003 """Create a generator that produces the data in the file size
1014 (default 65536) bytes at a time, up to optional limit (default is
1004 (default 65536) bytes at a time, up to optional limit (default is
1015 to read all data). Chunks may be less than size bytes if the
1005 to read all data). Chunks may be less than size bytes if the
1016 chunk is the last chunk in the file, or the file is a socket or
1006 chunk is the last chunk in the file, or the file is a socket or
1017 some other type of file that sometimes reads less data than is
1007 some other type of file that sometimes reads less data than is
1018 requested."""
1008 requested."""
1019 assert size >= 0
1009 assert size >= 0
1020 assert limit is None or limit >= 0
1010 assert limit is None or limit >= 0
1021 while True:
1011 while True:
1022 if limit is None:
1012 if limit is None:
1023 nbytes = size
1013 nbytes = size
1024 else:
1014 else:
1025 nbytes = min(limit, size)
1015 nbytes = min(limit, size)
1026 s = nbytes and f.read(nbytes)
1016 s = nbytes and f.read(nbytes)
1027 if not s:
1017 if not s:
1028 break
1018 break
1029 if limit:
1019 if limit:
1030 limit -= len(s)
1020 limit -= len(s)
1031 yield s
1021 yield s
1032
1022
1033 def makedate():
1023 def makedate():
1034 lt = time.localtime()
1024 lt = time.localtime()
1035 if lt[8] == 1 and time.daylight:
1025 if lt[8] == 1 and time.daylight:
1036 tz = time.altzone
1026 tz = time.altzone
1037 else:
1027 else:
1038 tz = time.timezone
1028 tz = time.timezone
1039 t = time.mktime(lt)
1029 t = time.mktime(lt)
1040 if t < 0:
1030 if t < 0:
1041 hint = _("check your clock")
1031 hint = _("check your clock")
1042 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1032 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1043 return t, tz
1033 return t, tz
1044
1034
1045 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1035 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1046 """represent a (unixtime, offset) tuple as a localized time.
1036 """represent a (unixtime, offset) tuple as a localized time.
1047 unixtime is seconds since the epoch, and offset is the time zone's
1037 unixtime is seconds since the epoch, and offset is the time zone's
1048 number of seconds away from UTC. if timezone is false, do not
1038 number of seconds away from UTC. if timezone is false, do not
1049 append time zone to string."""
1039 append time zone to string."""
1050 t, tz = date or makedate()
1040 t, tz = date or makedate()
1051 if t < 0:
1041 if t < 0:
1052 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1042 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1053 tz = 0
1043 tz = 0
1054 if "%1" in format or "%2" in format:
1044 if "%1" in format or "%2" in format:
1055 sign = (tz > 0) and "-" or "+"
1045 sign = (tz > 0) and "-" or "+"
1056 minutes = abs(tz) // 60
1046 minutes = abs(tz) // 60
1057 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1047 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1058 format = format.replace("%2", "%02d" % (minutes % 60))
1048 format = format.replace("%2", "%02d" % (minutes % 60))
1059 s = time.strftime(format, time.gmtime(float(t) - tz))
1049 s = time.strftime(format, time.gmtime(float(t) - tz))
1060 return s
1050 return s
1061
1051
1062 def shortdate(date=None):
1052 def shortdate(date=None):
1063 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1053 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1064 return datestr(date, format='%Y-%m-%d')
1054 return datestr(date, format='%Y-%m-%d')
1065
1055
1066 def strdate(string, format, defaults=[]):
1056 def strdate(string, format, defaults=[]):
1067 """parse a localized time string and return a (unixtime, offset) tuple.
1057 """parse a localized time string and return a (unixtime, offset) tuple.
1068 if the string cannot be parsed, ValueError is raised."""
1058 if the string cannot be parsed, ValueError is raised."""
1069 def timezone(string):
1059 def timezone(string):
1070 tz = string.split()[-1]
1060 tz = string.split()[-1]
1071 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1061 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1072 sign = (tz[0] == "+") and 1 or -1
1062 sign = (tz[0] == "+") and 1 or -1
1073 hours = int(tz[1:3])
1063 hours = int(tz[1:3])
1074 minutes = int(tz[3:5])
1064 minutes = int(tz[3:5])
1075 return -sign * (hours * 60 + minutes) * 60
1065 return -sign * (hours * 60 + minutes) * 60
1076 if tz == "GMT" or tz == "UTC":
1066 if tz == "GMT" or tz == "UTC":
1077 return 0
1067 return 0
1078 return None
1068 return None
1079
1069
1080 # NOTE: unixtime = localunixtime + offset
1070 # NOTE: unixtime = localunixtime + offset
1081 offset, date = timezone(string), string
1071 offset, date = timezone(string), string
1082 if offset is not None:
1072 if offset is not None:
1083 date = " ".join(string.split()[:-1])
1073 date = " ".join(string.split()[:-1])
1084
1074
1085 # add missing elements from defaults
1075 # add missing elements from defaults
1086 usenow = False # default to using biased defaults
1076 usenow = False # default to using biased defaults
1087 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1077 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1088 found = [True for p in part if ("%"+p) in format]
1078 found = [True for p in part if ("%"+p) in format]
1089 if not found:
1079 if not found:
1090 date += "@" + defaults[part][usenow]
1080 date += "@" + defaults[part][usenow]
1091 format += "@%" + part[0]
1081 format += "@%" + part[0]
1092 else:
1082 else:
1093 # We've found a specific time element, less specific time
1083 # We've found a specific time element, less specific time
1094 # elements are relative to today
1084 # elements are relative to today
1095 usenow = True
1085 usenow = True
1096
1086
1097 timetuple = time.strptime(date, format)
1087 timetuple = time.strptime(date, format)
1098 localunixtime = int(calendar.timegm(timetuple))
1088 localunixtime = int(calendar.timegm(timetuple))
1099 if offset is None:
1089 if offset is None:
1100 # local timezone
1090 # local timezone
1101 unixtime = int(time.mktime(timetuple))
1091 unixtime = int(time.mktime(timetuple))
1102 offset = unixtime - localunixtime
1092 offset = unixtime - localunixtime
1103 else:
1093 else:
1104 unixtime = localunixtime + offset
1094 unixtime = localunixtime + offset
1105 return unixtime, offset
1095 return unixtime, offset
1106
1096
1107 def parsedate(date, formats=None, bias={}):
1097 def parsedate(date, formats=None, bias={}):
1108 """parse a localized date/time and return a (unixtime, offset) tuple.
1098 """parse a localized date/time and return a (unixtime, offset) tuple.
1109
1099
1110 The date may be a "unixtime offset" string or in one of the specified
1100 The date may be a "unixtime offset" string or in one of the specified
1111 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1101 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1112 """
1102 """
1113 if not date:
1103 if not date:
1114 return 0, 0
1104 return 0, 0
1115 if isinstance(date, tuple) and len(date) == 2:
1105 if isinstance(date, tuple) and len(date) == 2:
1116 return date
1106 return date
1117 if not formats:
1107 if not formats:
1118 formats = defaultdateformats
1108 formats = defaultdateformats
1119 date = date.strip()
1109 date = date.strip()
1120 try:
1110 try:
1121 when, offset = map(int, date.split(' '))
1111 when, offset = map(int, date.split(' '))
1122 except ValueError:
1112 except ValueError:
1123 # fill out defaults
1113 # fill out defaults
1124 now = makedate()
1114 now = makedate()
1125 defaults = {}
1115 defaults = {}
1126 nowmap = {}
1116 nowmap = {}
1127 for part in ("d", "mb", "yY", "HI", "M", "S"):
1117 for part in ("d", "mb", "yY", "HI", "M", "S"):
1128 # this piece is for rounding the specific end of unknowns
1118 # this piece is for rounding the specific end of unknowns
1129 b = bias.get(part)
1119 b = bias.get(part)
1130 if b is None:
1120 if b is None:
1131 if part[0] in "HMS":
1121 if part[0] in "HMS":
1132 b = "00"
1122 b = "00"
1133 else:
1123 else:
1134 b = "0"
1124 b = "0"
1135
1125
1136 # this piece is for matching the generic end to today's date
1126 # this piece is for matching the generic end to today's date
1137 n = datestr(now, "%" + part[0])
1127 n = datestr(now, "%" + part[0])
1138
1128
1139 defaults[part] = (b, n)
1129 defaults[part] = (b, n)
1140
1130
1141 for format in formats:
1131 for format in formats:
1142 try:
1132 try:
1143 when, offset = strdate(date, format, defaults)
1133 when, offset = strdate(date, format, defaults)
1144 except (ValueError, OverflowError):
1134 except (ValueError, OverflowError):
1145 pass
1135 pass
1146 else:
1136 else:
1147 break
1137 break
1148 else:
1138 else:
1149 raise Abort(_('invalid date: %r') % date)
1139 raise Abort(_('invalid date: %r') % date)
1150 # validate explicit (probably user-specified) date and
1140 # validate explicit (probably user-specified) date and
1151 # time zone offset. values must fit in signed 32 bits for
1141 # time zone offset. values must fit in signed 32 bits for
1152 # current 32-bit linux runtimes. timezones go from UTC-12
1142 # current 32-bit linux runtimes. timezones go from UTC-12
1153 # to UTC+14
1143 # to UTC+14
1154 if abs(when) > 0x7fffffff:
1144 if abs(when) > 0x7fffffff:
1155 raise Abort(_('date exceeds 32 bits: %d') % when)
1145 raise Abort(_('date exceeds 32 bits: %d') % when)
1156 if when < 0:
1146 if when < 0:
1157 raise Abort(_('negative date value: %d') % when)
1147 raise Abort(_('negative date value: %d') % when)
1158 if offset < -50400 or offset > 43200:
1148 if offset < -50400 or offset > 43200:
1159 raise Abort(_('impossible time zone offset: %d') % offset)
1149 raise Abort(_('impossible time zone offset: %d') % offset)
1160 return when, offset
1150 return when, offset
1161
1151
1162 def matchdate(date):
1152 def matchdate(date):
1163 """Return a function that matches a given date match specifier
1153 """Return a function that matches a given date match specifier
1164
1154
1165 Formats include:
1155 Formats include:
1166
1156
1167 '{date}' match a given date to the accuracy provided
1157 '{date}' match a given date to the accuracy provided
1168
1158
1169 '<{date}' on or before a given date
1159 '<{date}' on or before a given date
1170
1160
1171 '>{date}' on or after a given date
1161 '>{date}' on or after a given date
1172
1162
1173 >>> p1 = parsedate("10:29:59")
1163 >>> p1 = parsedate("10:29:59")
1174 >>> p2 = parsedate("10:30:00")
1164 >>> p2 = parsedate("10:30:00")
1175 >>> p3 = parsedate("10:30:59")
1165 >>> p3 = parsedate("10:30:59")
1176 >>> p4 = parsedate("10:31:00")
1166 >>> p4 = parsedate("10:31:00")
1177 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1167 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1178 >>> f = matchdate("10:30")
1168 >>> f = matchdate("10:30")
1179 >>> f(p1[0])
1169 >>> f(p1[0])
1180 False
1170 False
1181 >>> f(p2[0])
1171 >>> f(p2[0])
1182 True
1172 True
1183 >>> f(p3[0])
1173 >>> f(p3[0])
1184 True
1174 True
1185 >>> f(p4[0])
1175 >>> f(p4[0])
1186 False
1176 False
1187 >>> f(p5[0])
1177 >>> f(p5[0])
1188 False
1178 False
1189 """
1179 """
1190
1180
1191 def lower(date):
1181 def lower(date):
1192 d = dict(mb="1", d="1")
1182 d = dict(mb="1", d="1")
1193 return parsedate(date, extendeddateformats, d)[0]
1183 return parsedate(date, extendeddateformats, d)[0]
1194
1184
1195 def upper(date):
1185 def upper(date):
1196 d = dict(mb="12", HI="23", M="59", S="59")
1186 d = dict(mb="12", HI="23", M="59", S="59")
1197 for days in ("31", "30", "29"):
1187 for days in ("31", "30", "29"):
1198 try:
1188 try:
1199 d["d"] = days
1189 d["d"] = days
1200 return parsedate(date, extendeddateformats, d)[0]
1190 return parsedate(date, extendeddateformats, d)[0]
1201 except:
1191 except:
1202 pass
1192 pass
1203 d["d"] = "28"
1193 d["d"] = "28"
1204 return parsedate(date, extendeddateformats, d)[0]
1194 return parsedate(date, extendeddateformats, d)[0]
1205
1195
1206 date = date.strip()
1196 date = date.strip()
1207 if date[0] == "<":
1197 if date[0] == "<":
1208 when = upper(date[1:])
1198 when = upper(date[1:])
1209 return lambda x: x <= when
1199 return lambda x: x <= when
1210 elif date[0] == ">":
1200 elif date[0] == ">":
1211 when = lower(date[1:])
1201 when = lower(date[1:])
1212 return lambda x: x >= when
1202 return lambda x: x >= when
1213 elif date[0] == "-":
1203 elif date[0] == "-":
1214 try:
1204 try:
1215 days = int(date[1:])
1205 days = int(date[1:])
1216 except ValueError:
1206 except ValueError:
1217 raise Abort(_("invalid day spec: %s") % date[1:])
1207 raise Abort(_("invalid day spec: %s") % date[1:])
1218 when = makedate()[0] - days * 3600 * 24
1208 when = makedate()[0] - days * 3600 * 24
1219 return lambda x: x >= when
1209 return lambda x: x >= when
1220 elif " to " in date:
1210 elif " to " in date:
1221 a, b = date.split(" to ")
1211 a, b = date.split(" to ")
1222 start, stop = lower(a), upper(b)
1212 start, stop = lower(a), upper(b)
1223 return lambda x: x >= start and x <= stop
1213 return lambda x: x >= start and x <= stop
1224 else:
1214 else:
1225 start, stop = lower(date), upper(date)
1215 start, stop = lower(date), upper(date)
1226 return lambda x: x >= start and x <= stop
1216 return lambda x: x >= start and x <= stop
1227
1217
1228 def shortuser(user):
1218 def shortuser(user):
1229 """Return a short representation of a user name or email address."""
1219 """Return a short representation of a user name or email address."""
1230 f = user.find('@')
1220 f = user.find('@')
1231 if f >= 0:
1221 if f >= 0:
1232 user = user[:f]
1222 user = user[:f]
1233 f = user.find('<')
1223 f = user.find('<')
1234 if f >= 0:
1224 if f >= 0:
1235 user = user[f + 1:]
1225 user = user[f + 1:]
1236 f = user.find(' ')
1226 f = user.find(' ')
1237 if f >= 0:
1227 if f >= 0:
1238 user = user[:f]
1228 user = user[:f]
1239 f = user.find('.')
1229 f = user.find('.')
1240 if f >= 0:
1230 if f >= 0:
1241 user = user[:f]
1231 user = user[:f]
1242 return user
1232 return user
1243
1233
1244 def email(author):
1234 def email(author):
1245 '''get email of author.'''
1235 '''get email of author.'''
1246 r = author.find('>')
1236 r = author.find('>')
1247 if r == -1:
1237 if r == -1:
1248 r = None
1238 r = None
1249 return author[author.find('<') + 1:r]
1239 return author[author.find('<') + 1:r]
1250
1240
1251 def _ellipsis(text, maxlength):
1241 def _ellipsis(text, maxlength):
1252 if len(text) <= maxlength:
1242 if len(text) <= maxlength:
1253 return text, False
1243 return text, False
1254 else:
1244 else:
1255 return "%s..." % (text[:maxlength - 3]), True
1245 return "%s..." % (text[:maxlength - 3]), True
1256
1246
1257 def ellipsis(text, maxlength=400):
1247 def ellipsis(text, maxlength=400):
1258 """Trim string to at most maxlength (default: 400) characters."""
1248 """Trim string to at most maxlength (default: 400) characters."""
1259 try:
1249 try:
1260 # use unicode not to split at intermediate multi-byte sequence
1250 # use unicode not to split at intermediate multi-byte sequence
1261 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1251 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1262 maxlength)
1252 maxlength)
1263 if not truncated:
1253 if not truncated:
1264 return text
1254 return text
1265 return utext.encode(encoding.encoding)
1255 return utext.encode(encoding.encoding)
1266 except (UnicodeDecodeError, UnicodeEncodeError):
1256 except (UnicodeDecodeError, UnicodeEncodeError):
1267 return _ellipsis(text, maxlength)[0]
1257 return _ellipsis(text, maxlength)[0]
1268
1258
1269 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1259 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1270 '''yield every hg repository under path, recursively.'''
1260 '''yield every hg repository under path, recursively.'''
1271 def errhandler(err):
1261 def errhandler(err):
1272 if err.filename == path:
1262 if err.filename == path:
1273 raise err
1263 raise err
1274 if followsym and hasattr(os.path, 'samestat'):
1264 if followsym and hasattr(os.path, 'samestat'):
1275 def _add_dir_if_not_there(dirlst, dirname):
1265 def _add_dir_if_not_there(dirlst, dirname):
1276 match = False
1266 match = False
1277 samestat = os.path.samestat
1267 samestat = os.path.samestat
1278 dirstat = os.stat(dirname)
1268 dirstat = os.stat(dirname)
1279 for lstdirstat in dirlst:
1269 for lstdirstat in dirlst:
1280 if samestat(dirstat, lstdirstat):
1270 if samestat(dirstat, lstdirstat):
1281 match = True
1271 match = True
1282 break
1272 break
1283 if not match:
1273 if not match:
1284 dirlst.append(dirstat)
1274 dirlst.append(dirstat)
1285 return not match
1275 return not match
1286 else:
1276 else:
1287 followsym = False
1277 followsym = False
1288
1278
1289 if (seen_dirs is None) and followsym:
1279 if (seen_dirs is None) and followsym:
1290 seen_dirs = []
1280 seen_dirs = []
1291 _add_dir_if_not_there(seen_dirs, path)
1281 _add_dir_if_not_there(seen_dirs, path)
1292 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1282 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1293 dirs.sort()
1283 dirs.sort()
1294 if '.hg' in dirs:
1284 if '.hg' in dirs:
1295 yield root # found a repository
1285 yield root # found a repository
1296 qroot = os.path.join(root, '.hg', 'patches')
1286 qroot = os.path.join(root, '.hg', 'patches')
1297 if os.path.isdir(os.path.join(qroot, '.hg')):
1287 if os.path.isdir(os.path.join(qroot, '.hg')):
1298 yield qroot # we have a patch queue repo here
1288 yield qroot # we have a patch queue repo here
1299 if recurse:
1289 if recurse:
1300 # avoid recursing inside the .hg directory
1290 # avoid recursing inside the .hg directory
1301 dirs.remove('.hg')
1291 dirs.remove('.hg')
1302 else:
1292 else:
1303 dirs[:] = [] # don't descend further
1293 dirs[:] = [] # don't descend further
1304 elif followsym:
1294 elif followsym:
1305 newdirs = []
1295 newdirs = []
1306 for d in dirs:
1296 for d in dirs:
1307 fname = os.path.join(root, d)
1297 fname = os.path.join(root, d)
1308 if _add_dir_if_not_there(seen_dirs, fname):
1298 if _add_dir_if_not_there(seen_dirs, fname):
1309 if os.path.islink(fname):
1299 if os.path.islink(fname):
1310 for hgname in walkrepos(fname, True, seen_dirs):
1300 for hgname in walkrepos(fname, True, seen_dirs):
1311 yield hgname
1301 yield hgname
1312 else:
1302 else:
1313 newdirs.append(d)
1303 newdirs.append(d)
1314 dirs[:] = newdirs
1304 dirs[:] = newdirs
1315
1305
1316 _rcpath = None
1306 _rcpath = None
1317
1307
1318 def os_rcpath():
1308 def os_rcpath():
1319 '''return default os-specific hgrc search path'''
1309 '''return default os-specific hgrc search path'''
1320 path = system_rcpath()
1310 path = system_rcpath()
1321 path.extend(user_rcpath())
1311 path.extend(user_rcpath())
1322 path = [os.path.normpath(f) for f in path]
1312 path = [os.path.normpath(f) for f in path]
1323 return path
1313 return path
1324
1314
1325 def rcpath():
1315 def rcpath():
1326 '''return hgrc search path. if env var HGRCPATH is set, use it.
1316 '''return hgrc search path. if env var HGRCPATH is set, use it.
1327 for each item in path, if directory, use files ending in .rc,
1317 for each item in path, if directory, use files ending in .rc,
1328 else use item.
1318 else use item.
1329 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1319 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1330 if no HGRCPATH, use default os-specific path.'''
1320 if no HGRCPATH, use default os-specific path.'''
1331 global _rcpath
1321 global _rcpath
1332 if _rcpath is None:
1322 if _rcpath is None:
1333 if 'HGRCPATH' in os.environ:
1323 if 'HGRCPATH' in os.environ:
1334 _rcpath = []
1324 _rcpath = []
1335 for p in os.environ['HGRCPATH'].split(os.pathsep):
1325 for p in os.environ['HGRCPATH'].split(os.pathsep):
1336 if not p:
1326 if not p:
1337 continue
1327 continue
1338 p = expandpath(p)
1328 p = expandpath(p)
1339 if os.path.isdir(p):
1329 if os.path.isdir(p):
1340 for f, kind in osutil.listdir(p):
1330 for f, kind in osutil.listdir(p):
1341 if f.endswith('.rc'):
1331 if f.endswith('.rc'):
1342 _rcpath.append(os.path.join(p, f))
1332 _rcpath.append(os.path.join(p, f))
1343 else:
1333 else:
1344 _rcpath.append(p)
1334 _rcpath.append(p)
1345 else:
1335 else:
1346 _rcpath = os_rcpath()
1336 _rcpath = os_rcpath()
1347 return _rcpath
1337 return _rcpath
1348
1338
1349 def bytecount(nbytes):
1339 def bytecount(nbytes):
1350 '''return byte count formatted as readable string, with units'''
1340 '''return byte count formatted as readable string, with units'''
1351
1341
1352 units = (
1342 units = (
1353 (100, 1 << 30, _('%.0f GB')),
1343 (100, 1 << 30, _('%.0f GB')),
1354 (10, 1 << 30, _('%.1f GB')),
1344 (10, 1 << 30, _('%.1f GB')),
1355 (1, 1 << 30, _('%.2f GB')),
1345 (1, 1 << 30, _('%.2f GB')),
1356 (100, 1 << 20, _('%.0f MB')),
1346 (100, 1 << 20, _('%.0f MB')),
1357 (10, 1 << 20, _('%.1f MB')),
1347 (10, 1 << 20, _('%.1f MB')),
1358 (1, 1 << 20, _('%.2f MB')),
1348 (1, 1 << 20, _('%.2f MB')),
1359 (100, 1 << 10, _('%.0f KB')),
1349 (100, 1 << 10, _('%.0f KB')),
1360 (10, 1 << 10, _('%.1f KB')),
1350 (10, 1 << 10, _('%.1f KB')),
1361 (1, 1 << 10, _('%.2f KB')),
1351 (1, 1 << 10, _('%.2f KB')),
1362 (1, 1, _('%.0f bytes')),
1352 (1, 1, _('%.0f bytes')),
1363 )
1353 )
1364
1354
1365 for multiplier, divisor, format in units:
1355 for multiplier, divisor, format in units:
1366 if nbytes >= divisor * multiplier:
1356 if nbytes >= divisor * multiplier:
1367 return format % (nbytes / float(divisor))
1357 return format % (nbytes / float(divisor))
1368 return units[-1][2] % nbytes
1358 return units[-1][2] % nbytes
1369
1359
1370 def drop_scheme(scheme, path):
1360 def drop_scheme(scheme, path):
1371 sc = scheme + ':'
1361 sc = scheme + ':'
1372 if path.startswith(sc):
1362 if path.startswith(sc):
1373 path = path[len(sc):]
1363 path = path[len(sc):]
1374 if path.startswith('//'):
1364 if path.startswith('//'):
1375 if scheme == 'file':
1365 if scheme == 'file':
1376 i = path.find('/', 2)
1366 i = path.find('/', 2)
1377 if i == -1:
1367 if i == -1:
1378 return ''
1368 return ''
1379 # On Windows, absolute paths are rooted at the current drive
1369 # On Windows, absolute paths are rooted at the current drive
1380 # root. On POSIX they are rooted at the file system root.
1370 # root. On POSIX they are rooted at the file system root.
1381 if os.name == 'nt':
1371 if os.name == 'nt':
1382 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1372 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1383 path = os.path.join(droot, path[i + 1:])
1373 path = os.path.join(droot, path[i + 1:])
1384 else:
1374 else:
1385 path = path[i:]
1375 path = path[i:]
1386 else:
1376 else:
1387 path = path[2:]
1377 path = path[2:]
1388 return path
1378 return path
1389
1379
1390 def uirepr(s):
1380 def uirepr(s):
1391 # Avoid double backslash in Windows path repr()
1381 # Avoid double backslash in Windows path repr()
1392 return repr(s).replace('\\\\', '\\')
1382 return repr(s).replace('\\\\', '\\')
1393
1383
1394 # delay import of textwrap
1384 # delay import of textwrap
1395 def MBTextWrapper(**kwargs):
1385 def MBTextWrapper(**kwargs):
1396 class tw(textwrap.TextWrapper):
1386 class tw(textwrap.TextWrapper):
1397 """
1387 """
1398 Extend TextWrapper for double-width characters.
1388 Extend TextWrapper for double-width characters.
1399
1389
1400 Some Asian characters use two terminal columns instead of one.
1390 Some Asian characters use two terminal columns instead of one.
1401 A good example of this behavior can be seen with u'\u65e5\u672c',
1391 A good example of this behavior can be seen with u'\u65e5\u672c',
1402 the two Japanese characters for "Japan":
1392 the two Japanese characters for "Japan":
1403 len() returns 2, but when printed to a terminal, they eat 4 columns.
1393 len() returns 2, but when printed to a terminal, they eat 4 columns.
1404
1394
1405 (Note that this has nothing to do whatsoever with unicode
1395 (Note that this has nothing to do whatsoever with unicode
1406 representation, or encoding of the underlying string)
1396 representation, or encoding of the underlying string)
1407 """
1397 """
1408 def __init__(self, **kwargs):
1398 def __init__(self, **kwargs):
1409 textwrap.TextWrapper.__init__(self, **kwargs)
1399 textwrap.TextWrapper.__init__(self, **kwargs)
1410
1400
1411 def _cutdown(self, str, space_left):
1401 def _cutdown(self, str, space_left):
1412 l = 0
1402 l = 0
1413 ucstr = unicode(str, encoding.encoding)
1403 ucstr = unicode(str, encoding.encoding)
1414 colwidth = unicodedata.east_asian_width
1404 colwidth = unicodedata.east_asian_width
1415 for i in xrange(len(ucstr)):
1405 for i in xrange(len(ucstr)):
1416 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1406 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1417 if space_left < l:
1407 if space_left < l:
1418 return (ucstr[:i].encode(encoding.encoding),
1408 return (ucstr[:i].encode(encoding.encoding),
1419 ucstr[i:].encode(encoding.encoding))
1409 ucstr[i:].encode(encoding.encoding))
1420 return str, ''
1410 return str, ''
1421
1411
1422 # overriding of base class
1412 # overriding of base class
1423 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1413 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1424 space_left = max(width - cur_len, 1)
1414 space_left = max(width - cur_len, 1)
1425
1415
1426 if self.break_long_words:
1416 if self.break_long_words:
1427 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1417 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1428 cur_line.append(cut)
1418 cur_line.append(cut)
1429 reversed_chunks[-1] = res
1419 reversed_chunks[-1] = res
1430 elif not cur_line:
1420 elif not cur_line:
1431 cur_line.append(reversed_chunks.pop())
1421 cur_line.append(reversed_chunks.pop())
1432
1422
1433 global MBTextWrapper
1423 global MBTextWrapper
1434 MBTextWrapper = tw
1424 MBTextWrapper = tw
1435 return tw(**kwargs)
1425 return tw(**kwargs)
1436
1426
1437 def wrap(line, width, initindent='', hangindent=''):
1427 def wrap(line, width, initindent='', hangindent=''):
1438 maxindent = max(len(hangindent), len(initindent))
1428 maxindent = max(len(hangindent), len(initindent))
1439 if width <= maxindent:
1429 if width <= maxindent:
1440 # adjust for weird terminal size
1430 # adjust for weird terminal size
1441 width = max(78, maxindent + 1)
1431 width = max(78, maxindent + 1)
1442 wrapper = MBTextWrapper(width=width,
1432 wrapper = MBTextWrapper(width=width,
1443 initial_indent=initindent,
1433 initial_indent=initindent,
1444 subsequent_indent=hangindent)
1434 subsequent_indent=hangindent)
1445 return wrapper.fill(line)
1435 return wrapper.fill(line)
1446
1436
1447 def iterlines(iterator):
1437 def iterlines(iterator):
1448 for chunk in iterator:
1438 for chunk in iterator:
1449 for line in chunk.splitlines():
1439 for line in chunk.splitlines():
1450 yield line
1440 yield line
1451
1441
1452 def expandpath(path):
1442 def expandpath(path):
1453 return os.path.expanduser(os.path.expandvars(path))
1443 return os.path.expanduser(os.path.expandvars(path))
1454
1444
1455 def hgcmd():
1445 def hgcmd():
1456 """Return the command used to execute current hg
1446 """Return the command used to execute current hg
1457
1447
1458 This is different from hgexecutable() because on Windows we want
1448 This is different from hgexecutable() because on Windows we want
1459 to avoid things opening new shell windows like batch files, so we
1449 to avoid things opening new shell windows like batch files, so we
1460 get either the python call or current executable.
1450 get either the python call or current executable.
1461 """
1451 """
1462 if main_is_frozen():
1452 if main_is_frozen():
1463 return [sys.executable]
1453 return [sys.executable]
1464 return gethgcmd()
1454 return gethgcmd()
1465
1455
1466 def rundetached(args, condfn):
1456 def rundetached(args, condfn):
1467 """Execute the argument list in a detached process.
1457 """Execute the argument list in a detached process.
1468
1458
1469 condfn is a callable which is called repeatedly and should return
1459 condfn is a callable which is called repeatedly and should return
1470 True once the child process is known to have started successfully.
1460 True once the child process is known to have started successfully.
1471 At this point, the child process PID is returned. If the child
1461 At this point, the child process PID is returned. If the child
1472 process fails to start or finishes before condfn() evaluates to
1462 process fails to start or finishes before condfn() evaluates to
1473 True, return -1.
1463 True, return -1.
1474 """
1464 """
1475 # Windows case is easier because the child process is either
1465 # Windows case is easier because the child process is either
1476 # successfully starting and validating the condition or exiting
1466 # successfully starting and validating the condition or exiting
1477 # on failure. We just poll on its PID. On Unix, if the child
1467 # on failure. We just poll on its PID. On Unix, if the child
1478 # process fails to start, it will be left in a zombie state until
1468 # process fails to start, it will be left in a zombie state until
1479 # the parent wait on it, which we cannot do since we expect a long
1469 # the parent wait on it, which we cannot do since we expect a long
1480 # running process on success. Instead we listen for SIGCHLD telling
1470 # running process on success. Instead we listen for SIGCHLD telling
1481 # us our child process terminated.
1471 # us our child process terminated.
1482 terminated = set()
1472 terminated = set()
1483 def handler(signum, frame):
1473 def handler(signum, frame):
1484 terminated.add(os.wait())
1474 terminated.add(os.wait())
1485 prevhandler = None
1475 prevhandler = None
1486 if hasattr(signal, 'SIGCHLD'):
1476 if hasattr(signal, 'SIGCHLD'):
1487 prevhandler = signal.signal(signal.SIGCHLD, handler)
1477 prevhandler = signal.signal(signal.SIGCHLD, handler)
1488 try:
1478 try:
1489 pid = spawndetached(args)
1479 pid = spawndetached(args)
1490 while not condfn():
1480 while not condfn():
1491 if ((pid in terminated or not testpid(pid))
1481 if ((pid in terminated or not testpid(pid))
1492 and not condfn()):
1482 and not condfn()):
1493 return -1
1483 return -1
1494 time.sleep(0.1)
1484 time.sleep(0.1)
1495 return pid
1485 return pid
1496 finally:
1486 finally:
1497 if prevhandler is not None:
1487 if prevhandler is not None:
1498 signal.signal(signal.SIGCHLD, prevhandler)
1488 signal.signal(signal.SIGCHLD, prevhandler)
1499
1489
1500 try:
1490 try:
1501 any, all = any, all
1491 any, all = any, all
1502 except NameError:
1492 except NameError:
1503 def any(iterable):
1493 def any(iterable):
1504 for i in iterable:
1494 for i in iterable:
1505 if i:
1495 if i:
1506 return True
1496 return True
1507 return False
1497 return False
1508
1498
1509 def all(iterable):
1499 def all(iterable):
1510 for i in iterable:
1500 for i in iterable:
1511 if not i:
1501 if not i:
1512 return False
1502 return False
1513 return True
1503 return True
1514
1504
1515 def interpolate(prefix, mapping, s, fn=None):
1505 def interpolate(prefix, mapping, s, fn=None):
1516 """Return the result of interpolating items in the mapping into string s.
1506 """Return the result of interpolating items in the mapping into string s.
1517
1507
1518 prefix is a single character string, or a two character string with
1508 prefix is a single character string, or a two character string with
1519 a backslash as the first character if the prefix needs to be escaped in
1509 a backslash as the first character if the prefix needs to be escaped in
1520 a regular expression.
1510 a regular expression.
1521
1511
1522 fn is an optional function that will be applied to the replacement text
1512 fn is an optional function that will be applied to the replacement text
1523 just before replacement.
1513 just before replacement.
1524 """
1514 """
1525 fn = fn or (lambda s: s)
1515 fn = fn or (lambda s: s)
1526 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1516 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1527 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1517 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1528
1518
1529 def getport(port):
1519 def getport(port):
1530 """Return the port for a given network service.
1520 """Return the port for a given network service.
1531
1521
1532 If port is an integer, it's returned as is. If it's a string, it's
1522 If port is an integer, it's returned as is. If it's a string, it's
1533 looked up using socket.getservbyname(). If there's no matching
1523 looked up using socket.getservbyname(). If there's no matching
1534 service, util.Abort is raised.
1524 service, util.Abort is raised.
1535 """
1525 """
1536 try:
1526 try:
1537 return int(port)
1527 return int(port)
1538 except ValueError:
1528 except ValueError:
1539 pass
1529 pass
1540
1530
1541 try:
1531 try:
1542 return socket.getservbyname(port)
1532 return socket.getservbyname(port)
1543 except socket.error:
1533 except socket.error:
1544 raise Abort(_("no port number associated with service '%s'") % port)
1534 raise Abort(_("no port number associated with service '%s'") % port)
1545
1535
1546 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1536 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1547 '0': False, 'no': False, 'false': False, 'off': False,
1537 '0': False, 'no': False, 'false': False, 'off': False,
1548 'never': False}
1538 'never': False}
1549
1539
1550 def parsebool(s):
1540 def parsebool(s):
1551 """Parse s into a boolean.
1541 """Parse s into a boolean.
1552
1542
1553 If s is not a valid boolean, returns None.
1543 If s is not a valid boolean, returns None.
1554 """
1544 """
1555 return _booleans.get(s.lower(), None)
1545 return _booleans.get(s.lower(), None)
@@ -1,183 +1,350 b''
1 # win32.py - utility functions that use win32 API
1 # win32.py - utility functions that use win32 API
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Utility functions that use win32 API.
8 import osutil, encoding
9 import ctypes, errno, os, struct, subprocess
10
11 _kernel32 = ctypes.windll.kernel32
12
13 _BOOL = ctypes.c_long
14 _WORD = ctypes.c_ushort
15 _DWORD = ctypes.c_ulong
16 _LPCSTR = _LPSTR = ctypes.c_char_p
17 _HANDLE = ctypes.c_void_p
18 _HWND = _HANDLE
19
20 _INVALID_HANDLE_VALUE = -1
21
22 # GetLastError
23 _ERROR_SUCCESS = 0
24 _ERROR_INVALID_PARAMETER = 87
25 _ERROR_INSUFFICIENT_BUFFER = 122
26
27 # WPARAM is defined as UINT_PTR (unsigned type)
28 # LPARAM is defined as LONG_PTR (signed type)
29 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
30 _WPARAM = ctypes.c_ulong
31 _LPARAM = ctypes.c_long
32 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
33 _WPARAM = ctypes.c_ulonglong
34 _LPARAM = ctypes.c_longlong
35
36 class _FILETIME(ctypes.Structure):
37 _fields_ = [('dwLowDateTime', _DWORD),
38 ('dwHighDateTime', _DWORD)]
39
40 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
41 _fields_ = [('dwFileAttributes', _DWORD),
42 ('ftCreationTime', _FILETIME),
43 ('ftLastAccessTime', _FILETIME),
44 ('ftLastWriteTime', _FILETIME),
45 ('dwVolumeSerialNumber', _DWORD),
46 ('nFileSizeHigh', _DWORD),
47 ('nFileSizeLow', _DWORD),
48 ('nNumberOfLinks', _DWORD),
49 ('nFileIndexHigh', _DWORD),
50 ('nFileIndexLow', _DWORD)]
51
52 # CreateFile
53 _FILE_SHARE_READ = 0x00000001
54 _FILE_SHARE_WRITE = 0x00000002
55 _FILE_SHARE_DELETE = 0x00000004
56
57 _OPEN_EXISTING = 3
58
59 # Process Security and Access Rights
60 _PROCESS_QUERY_INFORMATION = 0x0400
61
62 # GetExitCodeProcess
63 _STILL_ACTIVE = 259
64
65 # registry
66 _HKEY_CURRENT_USER = 0x80000001L
67 _HKEY_LOCAL_MACHINE = 0x80000002L
68 _KEY_READ = 0x20019
69 _REG_SZ = 1
70 _REG_DWORD = 4
9
71
10 Mark Hammond's win32all package allows better functionality on
72 class _STARTUPINFO(ctypes.Structure):
11 Windows. This module overrides definitions in util.py. If not
73 _fields_ = [('cb', _DWORD),
12 available, import of this module will fail, and generic code will be
74 ('lpReserved', _LPSTR),
13 used.
75 ('lpDesktop', _LPSTR),
14 """
76 ('lpTitle', _LPSTR),
77 ('dwX', _DWORD),
78 ('dwY', _DWORD),
79 ('dwXSize', _DWORD),
80 ('dwYSize', _DWORD),
81 ('dwXCountChars', _DWORD),
82 ('dwYCountChars', _DWORD),
83 ('dwFillAttribute', _DWORD),
84 ('dwFlags', _DWORD),
85 ('wShowWindow', _WORD),
86 ('cbReserved2', _WORD),
87 ('lpReserved2', ctypes.c_char_p),
88 ('hStdInput', _HANDLE),
89 ('hStdOutput', _HANDLE),
90 ('hStdError', _HANDLE)]
91
92 class _PROCESS_INFORMATION(ctypes.Structure):
93 _fields_ = [('hProcess', _HANDLE),
94 ('hThread', _HANDLE),
95 ('dwProcessId', _DWORD),
96 ('dwThreadId', _DWORD)]
97
98 _DETACHED_PROCESS = 0x00000008
99 _STARTF_USESHOWWINDOW = 0x00000001
100 _SW_HIDE = 0
15
101
16 import win32api
102 class _COORD(ctypes.Structure):
103 _fields_ = [('X', ctypes.c_short),
104 ('Y', ctypes.c_short)]
105
106 class _SMALL_RECT(ctypes.Structure):
107 _fields_ = [('Left', ctypes.c_short),
108 ('Top', ctypes.c_short),
109 ('Right', ctypes.c_short),
110 ('Bottom', ctypes.c_short)]
111
112 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
113 _fields_ = [('dwSize', _COORD),
114 ('dwCursorPosition', _COORD),
115 ('wAttributes', _WORD),
116 ('srWindow', _SMALL_RECT),
117 ('dwMaximumWindowSize', _COORD)]
17
118
18 import errno, os, sys, pywintypes, win32con, win32file, win32process
119 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
19 import winerror, win32gui, win32console
120
20 import osutil, encoding
121 def _raiseoserror(name):
21 from win32com.shell import shell, shellcon
122 err = ctypes.WinError()
123 raise OSError(err.errno, '%s: %s' % (name, err.strerror))
124
125 def _getfileinfo(name):
126 fh = _kernel32.CreateFileA(name, 0,
127 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
128 None, _OPEN_EXISTING, 0, None)
129 if fh == _INVALID_HANDLE_VALUE:
130 _raiseoserror(name)
131 try:
132 fi = _BY_HANDLE_FILE_INFORMATION()
133 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
134 _raiseoserror(name)
135 return fi
136 finally:
137 _kernel32.CloseHandle(fh)
22
138
23 def os_link(src, dst):
139 def os_link(src, dst):
24 try:
140 if not _kernel32.CreateHardLinkA(dst, src, None):
25 win32file.CreateHardLink(dst, src)
141 _raiseoserror(src)
26 except pywintypes.error:
27 raise OSError(errno.EINVAL, 'target implements hardlinks improperly')
28 except NotImplementedError: # Another fake error win Win98
29 raise OSError(errno.EINVAL, 'Hardlinking not supported')
30
142
31 def _getfileinfo(pathname):
143 def nlinks(name):
32 """Return number of hardlinks for the given file."""
144 '''return number of hardlinks for the given file'''
33 try:
145 return _getfileinfo(name).nNumberOfLinks
34 fh = win32file.CreateFile(pathname, 0,
35 win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE |
36 win32file.FILE_SHARE_DELETE,
37 None, win32file.OPEN_EXISTING, 0, None)
38 except pywintypes.error:
39 raise OSError(errno.ENOENT, 'The system cannot find the file specified')
40 try:
41 return win32file.GetFileInformationByHandle(fh)
42 finally:
43 fh.Close()
44
45 def nlinks(pathname):
46 """Return number of hardlinks for the given file."""
47 return _getfileinfo(pathname)[7]
48
146
49 def samefile(fpath1, fpath2):
147 def samefile(fpath1, fpath2):
50 """Returns whether fpath1 and fpath2 refer to the same file. This is only
148 '''Returns whether fpath1 and fpath2 refer to the same file. This is only
51 guaranteed to work for files, not directories."""
149 guaranteed to work for files, not directories.'''
52 res1 = _getfileinfo(fpath1)
150 res1 = _getfileinfo(fpath1)
53 res2 = _getfileinfo(fpath2)
151 res2 = _getfileinfo(fpath2)
54 # Index 4 is the volume serial number, and 8 and 9 contain the file ID
152 return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
55 return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9]
153 and res1.nFileIndexHigh == res2.nFileIndexHigh
154 and res1.nFileIndexLow == res2.nFileIndexLow)
56
155
57 def samedevice(fpath1, fpath2):
156 def samedevice(fpath1, fpath2):
58 """Returns whether fpath1 and fpath2 are on the same device. This is only
157 '''Returns whether fpath1 and fpath2 are on the same device. This is only
59 guaranteed to work for files, not directories."""
158 guaranteed to work for files, not directories.'''
60 res1 = _getfileinfo(fpath1)
159 res1 = _getfileinfo(fpath1)
61 res2 = _getfileinfo(fpath2)
160 res2 = _getfileinfo(fpath2)
62 return res1[4] == res2[4]
161 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
63
162
64 def testpid(pid):
163 def testpid(pid):
65 '''return True if pid is still running or unable to
164 '''return True if pid is still running or unable to
66 determine, False otherwise'''
165 determine, False otherwise'''
67 try:
166 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
68 handle = win32api.OpenProcess(
167 if h:
69 win32con.PROCESS_QUERY_INFORMATION, False, pid)
168 try:
70 if handle:
169 status = _DWORD()
71 status = win32process.GetExitCodeProcess(handle)
170 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)):
72 return status == win32con.STILL_ACTIVE
171 return status.value == _STILL_ACTIVE
73 except pywintypes.error, details:
172 finally:
74 return details[0] != winerror.ERROR_INVALID_PARAMETER
173 _kernel32.CloseHandle(h)
75 return True
174 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
76
175
77 def lookup_reg(key, valname=None, scope=None):
176 def lookup_reg(key, valname=None, scope=None):
78 ''' Look up a key/value name in the Windows registry.
177 ''' Look up a key/value name in the Windows registry.
79
178
80 valname: value name. If unspecified, the default value for the key
179 valname: value name. If unspecified, the default value for the key
81 is used.
180 is used.
82 scope: optionally specify scope for registry lookup, this can be
181 scope: optionally specify scope for registry lookup, this can be
83 a sequence of scopes to look up in order. Default (CURRENT_USER,
182 a sequence of scopes to look up in order. Default (CURRENT_USER,
84 LOCAL_MACHINE).
183 LOCAL_MACHINE).
85 '''
184 '''
86 try:
185 adv = ctypes.windll.advapi32
87 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \
186 byref = ctypes.byref
88 QueryValueEx, OpenKey
89 except ImportError:
90 return None
91
92 if scope is None:
187 if scope is None:
93 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
188 scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE)
94 elif not isinstance(scope, (list, tuple)):
189 elif not isinstance(scope, (list, tuple)):
95 scope = (scope,)
190 scope = (scope,)
96 for s in scope:
191 for s in scope:
192 kh = _HANDLE()
193 res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh))
194 if res != _ERROR_SUCCESS:
195 continue
97 try:
196 try:
98 val = QueryValueEx(OpenKey(s, key), valname)[0]
197 size = _DWORD(600)
99 # never let a Unicode string escape into the wild
198 type = _DWORD()
100 return encoding.tolocal(val.encode('UTF-8'))
199 buf = ctypes.create_string_buffer(size.value + 1)
101 except EnvironmentError:
200 res = adv.RegQueryValueExA(kh.value, valname, None,
102 pass
201 byref(type), buf, byref(size))
202 if res != _ERROR_SUCCESS:
203 continue
204 if type.value == _REG_SZ:
205 # never let a Unicode string escape into the wild
206 return encoding.tolocal(buf.value.encode('UTF-8'))
207 elif type.value == _REG_DWORD:
208 fmt = '<L'
209 s = ctypes.string_at(byref(buf), struct.calcsize(fmt))
210 return struct.unpack(fmt, s)[0]
211 finally:
212 adv.RegCloseKey(kh.value)
103
213
104 def system_rcpath_win32():
214 def system_rcpath_win32():
105 '''return default os-specific hgrc search path'''
215 '''return default os-specific hgrc search path'''
106 filename = win32api.GetModuleFileName(0)
216 rcpath = []
217 size = 600
218 buf = ctypes.create_string_buffer(size + 1)
219 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size)
220 if len == 0:
221 raise ctypes.WinError()
222 elif len == size:
223 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER)
224 filename = buf.value
107 # Use mercurial.ini found in directory with hg.exe
225 # Use mercurial.ini found in directory with hg.exe
108 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
226 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
109 if os.path.isfile(progrc):
227 if os.path.isfile(progrc):
110 return [progrc]
228 rcpath.append(progrc)
229 return rcpath
111 # Use hgrc.d found in directory with hg.exe
230 # Use hgrc.d found in directory with hg.exe
112 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
231 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
113 if os.path.isdir(progrcd):
232 if os.path.isdir(progrcd):
114 rcpath = []
115 for f, kind in osutil.listdir(progrcd):
233 for f, kind in osutil.listdir(progrcd):
116 if f.endswith('.rc'):
234 if f.endswith('.rc'):
117 rcpath.append(os.path.join(progrcd, f))
235 rcpath.append(os.path.join(progrcd, f))
118 return rcpath
236 return rcpath
119 # else look for a system rcpath in the registry
237 # else look for a system rcpath in the registry
120 try:
238 value = lookup_reg('SOFTWARE\\Mercurial', None, _HKEY_LOCAL_MACHINE)
121 value = win32api.RegQueryValue(
239 if not isinstance(value, str) or not value:
122 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
123 rcpath = []
124 for p in value.split(os.pathsep):
125 if p.lower().endswith('mercurial.ini'):
126 rcpath.append(p)
127 elif os.path.isdir(p):
128 for f, kind in osutil.listdir(p):
129 if f.endswith('.rc'):
130 rcpath.append(os.path.join(p, f))
131 return rcpath
240 return rcpath
132 except pywintypes.error:
241 value = value.replace('/', os.sep)
133 return []
242 for p in value.split(os.pathsep):
243 if p.lower().endswith('mercurial.ini'):
244 rcpath.append(p)
245 elif os.path.isdir(p):
246 for f, kind in osutil.listdir(p):
247 if f.endswith('.rc'):
248 rcpath.append(os.path.join(p, f))
249 return rcpath
134
250
135 def user_rcpath_win32():
251 def user_rcpath_win32():
136 '''return os-specific hgrc search path to the user dir'''
252 '''return os-specific hgrc search path to the user dir'''
137 userdir = os.path.expanduser('~')
253 userdir = os.path.expanduser('~')
138 if sys.getwindowsversion()[3] != 2 and userdir == '~':
139 # We are on win < nt: fetch the APPDATA directory location and use
140 # the parent directory as the user home dir.
141 appdir = shell.SHGetPathFromIDList(
142 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
143 userdir = os.path.dirname(appdir)
144 return [os.path.join(userdir, 'mercurial.ini'),
254 return [os.path.join(userdir, 'mercurial.ini'),
145 os.path.join(userdir, '.hgrc')]
255 os.path.join(userdir, '.hgrc')]
146
256
147 def getuser():
257 def getuser():
148 '''return name of current user'''
258 '''return name of current user'''
149 return win32api.GetUserName()
259 adv = ctypes.windll.advapi32
260 size = _DWORD(300)
261 buf = ctypes.create_string_buffer(size.value + 1)
262 if not adv.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)):
263 raise ctypes.WinError()
264 return buf.value
150
265
151 def set_signal_handler_win32():
266 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD)
152 """Register a termination handler for console events including
267 _signal_handler = []
268
269 def set_signal_handler():
270 '''Register a termination handler for console events including
153 CTRL+C. python signal handlers do not work well with socket
271 CTRL+C. python signal handlers do not work well with socket
154 operations.
272 operations.
155 """
273 '''
156 def handler(event):
274 def handler(event):
157 win32process.ExitProcess(1)
275 _kernel32.ExitProcess(1)
158 win32api.SetConsoleCtrlHandler(handler)
276
277 if _signal_handler:
278 return # already registered
279 h = _SIGNAL_HANDLER(handler)
280 _signal_handler.append(h) # needed to prevent garbage collection
281 if not _kernel32.SetConsoleCtrlHandler(h, True):
282 raise ctypes.WinError()
283
284 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM)
159
285
160 def hidewindow():
286 def hidewindow():
161 def callback(*args, **kwargs):
287 user32 = ctypes.windll.user32
162 hwnd, pid = args
163 wpid = win32process.GetWindowThreadProcessId(hwnd)[1]
164 if pid == wpid:
165 win32gui.ShowWindow(hwnd, win32con.SW_HIDE)
166
288
167 pid = win32process.GetCurrentProcessId()
289 def callback(hwnd, pid):
168 win32gui.EnumWindows(callback, pid)
290 wpid = _DWORD()
291 user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
292 if pid == wpid.value:
293 user32.ShowWindow(hwnd, _SW_HIDE)
294 return False # stop enumerating windows
295 return True
296
297 pid = _kernel32.GetCurrentProcessId()
298 user32.EnumWindows(_WNDENUMPROC(callback), pid)
169
299
170 def termwidth():
300 def termwidth():
171 try:
301 # cmd.exe does not handle CR like a unix console, the CR is
172 # Query stderr to avoid problems with redirections
302 # counted in the line length. On 80 columns consoles, if 80
173 screenbuf = win32console.GetStdHandle(win32console.STD_ERROR_HANDLE)
303 # characters are written, the following CR won't apply on the
174 if screenbuf is None:
304 # current line but on the new one. Keep room for it.
175 return 79
305 width = 79
176 try:
306 # Query stderr to avoid problems with redirections
177 window = screenbuf.GetConsoleScreenBufferInfo()['Window']
307 screenbuf = _kernel32.GetStdHandle(
178 width = window.Right - window.Left
308 _STD_ERROR_HANDLE) # don't close the handle returned
179 return width
309 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE:
180 finally:
310 return width
181 screenbuf.Detach()
311 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
182 except pywintypes.error:
312 if not _kernel32.GetConsoleScreenBufferInfo(
183 return 79
313 screenbuf, ctypes.byref(csbi)):
314 return width
315 width = csbi.srWindow.Right - csbi.srWindow.Left
316 return width
317
318 def spawndetached(args):
319 # No standard library function really spawns a fully detached
320 # process under win32 because they allocate pipes or other objects
321 # to handle standard streams communications. Passing these objects
322 # to the child process requires handle inheritance to be enabled
323 # which makes really detached processes impossible.
324 si = _STARTUPINFO()
325 si.cb = ctypes.sizeof(_STARTUPINFO)
326 si.dwFlags = _STARTF_USESHOWWINDOW
327 si.wShowWindow = _SW_HIDE
328
329 pi = _PROCESS_INFORMATION()
330
331 env = ''
332 for k in os.environ:
333 env += "%s=%s\0" % (k, os.environ[k])
334 if not env:
335 env = '\0'
336 env += '\0'
337
338 args = subprocess.list2cmdline(args)
339 # Not running the command in shell mode makes python26 hang when
340 # writing to hgweb output socket.
341 comspec = os.environ.get("COMSPEC", "cmd.exe")
342 args = comspec + " /c " + args
343
344 res = _kernel32.CreateProcessA(
345 None, args, None, None, False, _DETACHED_PROCESS,
346 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
347 if not res:
348 raise ctypes.WinError()
349
350 return pi.dwProcessId
@@ -1,389 +1,339 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import osutil, error
9 import osutil, error
10 import errno, msvcrt, os, re, sys, random, subprocess
10 import errno, msvcrt, os, re, sys, random, subprocess
11
11
12 nulldev = 'NUL:'
12 nulldev = 'NUL:'
13 umask = 002
13 umask = 002
14
14
15 # wrap osutil.posixfile to provide friendlier exceptions
15 # wrap osutil.posixfile to provide friendlier exceptions
16 def posixfile(name, mode='r', buffering=-1):
16 def posixfile(name, mode='r', buffering=-1):
17 try:
17 try:
18 return osutil.posixfile(name, mode, buffering)
18 return osutil.posixfile(name, mode, buffering)
19 except WindowsError, err:
19 except WindowsError, err:
20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
21 posixfile.__doc__ = osutil.posixfile.__doc__
21 posixfile.__doc__ = osutil.posixfile.__doc__
22
22
23 class winstdout(object):
23 class winstdout(object):
24 '''stdout on windows misbehaves if sent through a pipe'''
24 '''stdout on windows misbehaves if sent through a pipe'''
25
25
26 def __init__(self, fp):
26 def __init__(self, fp):
27 self.fp = fp
27 self.fp = fp
28
28
29 def __getattr__(self, key):
29 def __getattr__(self, key):
30 return getattr(self.fp, key)
30 return getattr(self.fp, key)
31
31
32 def close(self):
32 def close(self):
33 try:
33 try:
34 self.fp.close()
34 self.fp.close()
35 except: pass
35 except: pass
36
36
37 def write(self, s):
37 def write(self, s):
38 try:
38 try:
39 # This is workaround for "Not enough space" error on
39 # This is workaround for "Not enough space" error on
40 # writing large size of data to console.
40 # writing large size of data to console.
41 limit = 16000
41 limit = 16000
42 l = len(s)
42 l = len(s)
43 start = 0
43 start = 0
44 self.softspace = 0
44 self.softspace = 0
45 while start < l:
45 while start < l:
46 end = start + limit
46 end = start + limit
47 self.fp.write(s[start:end])
47 self.fp.write(s[start:end])
48 start = end
48 start = end
49 except IOError, inst:
49 except IOError, inst:
50 if inst.errno != 0:
50 if inst.errno != 0:
51 raise
51 raise
52 self.close()
52 self.close()
53 raise IOError(errno.EPIPE, 'Broken pipe')
53 raise IOError(errno.EPIPE, 'Broken pipe')
54
54
55 def flush(self):
55 def flush(self):
56 try:
56 try:
57 return self.fp.flush()
57 return self.fp.flush()
58 except IOError, inst:
58 except IOError, inst:
59 if inst.errno != errno.EINVAL:
59 if inst.errno != errno.EINVAL:
60 raise
60 raise
61 self.close()
61 self.close()
62 raise IOError(errno.EPIPE, 'Broken pipe')
62 raise IOError(errno.EPIPE, 'Broken pipe')
63
63
64 sys.stdout = winstdout(sys.stdout)
64 sys.stdout = winstdout(sys.stdout)
65
65
66 def _is_win_9x():
66 def _is_win_9x():
67 '''return true if run on windows 95, 98 or me.'''
67 '''return true if run on windows 95, 98 or me.'''
68 try:
68 try:
69 return sys.getwindowsversion()[3] == 1
69 return sys.getwindowsversion()[3] == 1
70 except AttributeError:
70 except AttributeError:
71 return 'command' in os.environ.get('comspec', '')
71 return 'command' in os.environ.get('comspec', '')
72
72
73 def openhardlinks():
73 def openhardlinks():
74 return not _is_win_9x() and "win32api" in globals()
74 return not _is_win_9x()
75
75
76 def system_rcpath():
76 def system_rcpath():
77 try:
77 try:
78 return system_rcpath_win32()
78 return system_rcpath_win32()
79 except:
79 except:
80 return [r'c:\mercurial\mercurial.ini']
80 return [r'c:\mercurial\mercurial.ini']
81
81
82 def user_rcpath():
82 def user_rcpath():
83 '''return os-specific hgrc search path to the user dir'''
83 '''return os-specific hgrc search path to the user dir'''
84 try:
84 try:
85 path = user_rcpath_win32()
85 path = user_rcpath_win32()
86 except:
86 except:
87 home = os.path.expanduser('~')
87 home = os.path.expanduser('~')
88 path = [os.path.join(home, 'mercurial.ini'),
88 path = [os.path.join(home, 'mercurial.ini'),
89 os.path.join(home, '.hgrc')]
89 os.path.join(home, '.hgrc')]
90 userprofile = os.environ.get('USERPROFILE')
90 userprofile = os.environ.get('USERPROFILE')
91 if userprofile:
91 if userprofile:
92 path.append(os.path.join(userprofile, 'mercurial.ini'))
92 path.append(os.path.join(userprofile, 'mercurial.ini'))
93 path.append(os.path.join(userprofile, '.hgrc'))
93 path.append(os.path.join(userprofile, '.hgrc'))
94 return path
94 return path
95
95
96 def parse_patch_output(output_line):
96 def parse_patch_output(output_line):
97 """parses the output produced by patch and returns the filename"""
97 """parses the output produced by patch and returns the filename"""
98 pf = output_line[14:]
98 pf = output_line[14:]
99 if pf[0] == '`':
99 if pf[0] == '`':
100 pf = pf[1:-1] # Remove the quotes
100 pf = pf[1:-1] # Remove the quotes
101 return pf
101 return pf
102
102
103 def sshargs(sshcmd, host, user, port):
103 def sshargs(sshcmd, host, user, port):
104 '''Build argument list for ssh or Plink'''
104 '''Build argument list for ssh or Plink'''
105 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
105 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
106 args = user and ("%s@%s" % (user, host)) or host
106 args = user and ("%s@%s" % (user, host)) or host
107 return port and ("%s %s %s" % (args, pflag, port)) or args
107 return port and ("%s %s %s" % (args, pflag, port)) or args
108
108
109 def testpid(pid):
110 '''return False if pid dead, True if running or not known'''
111 return True
112
113 def set_flags(f, l, x):
109 def set_flags(f, l, x):
114 pass
110 pass
115
111
116 def set_binary(fd):
112 def set_binary(fd):
117 # When run without console, pipes may expose invalid
113 # When run without console, pipes may expose invalid
118 # fileno(), usually set to -1.
114 # fileno(), usually set to -1.
119 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
115 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
120 msvcrt.setmode(fd.fileno(), os.O_BINARY)
116 msvcrt.setmode(fd.fileno(), os.O_BINARY)
121
117
122 def pconvert(path):
118 def pconvert(path):
123 return '/'.join(path.split(os.sep))
119 return '/'.join(path.split(os.sep))
124
120
125 def localpath(path):
121 def localpath(path):
126 return path.replace('/', '\\')
122 return path.replace('/', '\\')
127
123
128 def normpath(path):
124 def normpath(path):
129 return pconvert(os.path.normpath(path))
125 return pconvert(os.path.normpath(path))
130
126
131 def realpath(path):
127 def realpath(path):
132 '''
128 '''
133 Returns the true, canonical file system path equivalent to the given
129 Returns the true, canonical file system path equivalent to the given
134 path.
130 path.
135 '''
131 '''
136 # TODO: There may be a more clever way to do this that also handles other,
132 # TODO: There may be a more clever way to do this that also handles other,
137 # less common file systems.
133 # less common file systems.
138 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
134 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
139
135
140 def samestat(s1, s2):
136 def samestat(s1, s2):
141 return False
137 return False
142
138
143 # A sequence of backslashes is special iff it precedes a double quote:
139 # A sequence of backslashes is special iff it precedes a double quote:
144 # - if there's an even number of backslashes, the double quote is not
140 # - if there's an even number of backslashes, the double quote is not
145 # quoted (i.e. it ends the quoted region)
141 # quoted (i.e. it ends the quoted region)
146 # - if there's an odd number of backslashes, the double quote is quoted
142 # - if there's an odd number of backslashes, the double quote is quoted
147 # - in both cases, every pair of backslashes is unquoted into a single
143 # - in both cases, every pair of backslashes is unquoted into a single
148 # backslash
144 # backslash
149 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
145 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
150 # So, to quote a string, we must surround it in double quotes, double
146 # So, to quote a string, we must surround it in double quotes, double
151 # the number of backslashes that preceed double quotes and add another
147 # the number of backslashes that preceed double quotes and add another
152 # backslash before every double quote (being careful with the double
148 # backslash before every double quote (being careful with the double
153 # quote we've appended to the end)
149 # quote we've appended to the end)
154 _quotere = None
150 _quotere = None
155 def shellquote(s):
151 def shellquote(s):
156 global _quotere
152 global _quotere
157 if _quotere is None:
153 if _quotere is None:
158 _quotere = re.compile(r'(\\*)("|\\$)')
154 _quotere = re.compile(r'(\\*)("|\\$)')
159 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
155 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
160
156
161 def quotecommand(cmd):
157 def quotecommand(cmd):
162 """Build a command string suitable for os.popen* calls."""
158 """Build a command string suitable for os.popen* calls."""
163 if sys.version_info < (2, 7, 1):
159 if sys.version_info < (2, 7, 1):
164 # Python versions since 2.7.1 do this extra quoting themselves
160 # Python versions since 2.7.1 do this extra quoting themselves
165 return '"' + cmd + '"'
161 return '"' + cmd + '"'
166 return cmd
162 return cmd
167
163
168 def popen(command, mode='r'):
164 def popen(command, mode='r'):
169 # Work around "popen spawned process may not write to stdout
165 # Work around "popen spawned process may not write to stdout
170 # under windows"
166 # under windows"
171 # http://bugs.python.org/issue1366
167 # http://bugs.python.org/issue1366
172 command += " 2> %s" % nulldev
168 command += " 2> %s" % nulldev
173 return os.popen(quotecommand(command), mode)
169 return os.popen(quotecommand(command), mode)
174
170
175 def explain_exit(code):
171 def explain_exit(code):
176 return _("exited with status %d") % code, code
172 return _("exited with status %d") % code, code
177
173
178 # if you change this stub into a real check, please try to implement the
174 # if you change this stub into a real check, please try to implement the
179 # username and groupname functions above, too.
175 # username and groupname functions above, too.
180 def isowner(st):
176 def isowner(st):
181 return True
177 return True
182
178
183 def find_exe(command):
179 def find_exe(command):
184 '''Find executable for command searching like cmd.exe does.
180 '''Find executable for command searching like cmd.exe does.
185 If command is a basename then PATH is searched for command.
181 If command is a basename then PATH is searched for command.
186 PATH isn't searched if command is an absolute or relative path.
182 PATH isn't searched if command is an absolute or relative path.
187 An extension from PATHEXT is found and added if not present.
183 An extension from PATHEXT is found and added if not present.
188 If command isn't found None is returned.'''
184 If command isn't found None is returned.'''
189 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
185 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
190 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
186 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
191 if os.path.splitext(command)[1].lower() in pathexts:
187 if os.path.splitext(command)[1].lower() in pathexts:
192 pathexts = ['']
188 pathexts = ['']
193
189
194 def findexisting(pathcommand):
190 def findexisting(pathcommand):
195 'Will append extension (if needed) and return existing file'
191 'Will append extension (if needed) and return existing file'
196 for ext in pathexts:
192 for ext in pathexts:
197 executable = pathcommand + ext
193 executable = pathcommand + ext
198 if os.path.exists(executable):
194 if os.path.exists(executable):
199 return executable
195 return executable
200 return None
196 return None
201
197
202 if os.sep in command:
198 if os.sep in command:
203 return findexisting(command)
199 return findexisting(command)
204
200
205 for path in os.environ.get('PATH', '').split(os.pathsep):
201 for path in os.environ.get('PATH', '').split(os.pathsep):
206 executable = findexisting(os.path.join(path, command))
202 executable = findexisting(os.path.join(path, command))
207 if executable is not None:
203 if executable is not None:
208 return executable
204 return executable
209 return findexisting(os.path.expanduser(os.path.expandvars(command)))
205 return findexisting(os.path.expanduser(os.path.expandvars(command)))
210
206
211 def set_signal_handler():
212 try:
213 set_signal_handler_win32()
214 except NameError:
215 pass
216
217 def statfiles(files):
207 def statfiles(files):
218 '''Stat each file in files and yield stat or None if file does not exist.
208 '''Stat each file in files and yield stat or None if file does not exist.
219 Cluster and cache stat per directory to minimize number of OS stat calls.'''
209 Cluster and cache stat per directory to minimize number of OS stat calls.'''
220 ncase = os.path.normcase
210 ncase = os.path.normcase
221 dircache = {} # dirname -> filename -> status | None if file does not exist
211 dircache = {} # dirname -> filename -> status | None if file does not exist
222 for nf in files:
212 for nf in files:
223 nf = ncase(nf)
213 nf = ncase(nf)
224 dir, base = os.path.split(nf)
214 dir, base = os.path.split(nf)
225 if not dir:
215 if not dir:
226 dir = '.'
216 dir = '.'
227 cache = dircache.get(dir, None)
217 cache = dircache.get(dir, None)
228 if cache is None:
218 if cache is None:
229 try:
219 try:
230 dmap = dict([(ncase(n), s)
220 dmap = dict([(ncase(n), s)
231 for n, k, s in osutil.listdir(dir, True)])
221 for n, k, s in osutil.listdir(dir, True)])
232 except OSError, err:
222 except OSError, err:
233 # handle directory not found in Python version prior to 2.5
223 # handle directory not found in Python version prior to 2.5
234 # Python <= 2.4 returns native Windows code 3 in errno
224 # Python <= 2.4 returns native Windows code 3 in errno
235 # Python >= 2.5 returns ENOENT and adds winerror field
225 # Python >= 2.5 returns ENOENT and adds winerror field
236 # EINVAL is raised if dir is not a directory.
226 # EINVAL is raised if dir is not a directory.
237 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
227 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
238 errno.ENOTDIR):
228 errno.ENOTDIR):
239 raise
229 raise
240 dmap = {}
230 dmap = {}
241 cache = dircache.setdefault(dir, dmap)
231 cache = dircache.setdefault(dir, dmap)
242 yield cache.get(base, None)
232 yield cache.get(base, None)
243
233
244 def getuser():
245 '''return name of current user'''
246 raise error.Abort(_('user name not available - set USERNAME '
247 'environment variable'))
248
249 def username(uid=None):
234 def username(uid=None):
250 """Return the name of the user with the given uid.
235 """Return the name of the user with the given uid.
251
236
252 If uid is None, return the name of the current user."""
237 If uid is None, return the name of the current user."""
253 return None
238 return None
254
239
255 def groupname(gid=None):
240 def groupname(gid=None):
256 """Return the name of the group with the given gid.
241 """Return the name of the group with the given gid.
257
242
258 If gid is None, return the name of the current group."""
243 If gid is None, return the name of the current group."""
259 return None
244 return None
260
245
261 def _removedirs(name):
246 def _removedirs(name):
262 """special version of os.removedirs that does not remove symlinked
247 """special version of os.removedirs that does not remove symlinked
263 directories or junction points if they actually contain files"""
248 directories or junction points if they actually contain files"""
264 if osutil.listdir(name):
249 if osutil.listdir(name):
265 return
250 return
266 os.rmdir(name)
251 os.rmdir(name)
267 head, tail = os.path.split(name)
252 head, tail = os.path.split(name)
268 if not tail:
253 if not tail:
269 head, tail = os.path.split(head)
254 head, tail = os.path.split(head)
270 while head and tail:
255 while head and tail:
271 try:
256 try:
272 if osutil.listdir(head):
257 if osutil.listdir(head):
273 return
258 return
274 os.rmdir(head)
259 os.rmdir(head)
275 except:
260 except:
276 break
261 break
277 head, tail = os.path.split(head)
262 head, tail = os.path.split(head)
278
263
279 def unlinkpath(f):
264 def unlinkpath(f):
280 """unlink and remove the directory if it is empty"""
265 """unlink and remove the directory if it is empty"""
281 os.unlink(f)
266 os.unlink(f)
282 # try removing directories that might now be empty
267 # try removing directories that might now be empty
283 try:
268 try:
284 _removedirs(os.path.dirname(f))
269 _removedirs(os.path.dirname(f))
285 except OSError:
270 except OSError:
286 pass
271 pass
287
272
288 def unlink(f):
273 def unlink(f):
289 '''try to implement POSIX' unlink semantics on Windows'''
274 '''try to implement POSIX' unlink semantics on Windows'''
290
275
291 # POSIX allows to unlink and rename open files. Windows has serious
276 # POSIX allows to unlink and rename open files. Windows has serious
292 # problems with doing that:
277 # problems with doing that:
293 # - Calling os.unlink (or os.rename) on a file f fails if f or any
278 # - Calling os.unlink (or os.rename) on a file f fails if f or any
294 # hardlinked copy of f has been opened with Python's open(). There is no
279 # hardlinked copy of f has been opened with Python's open(). There is no
295 # way such a file can be deleted or renamed on Windows (other than
280 # way such a file can be deleted or renamed on Windows (other than
296 # scheduling the delete or rename for the next reboot).
281 # scheduling the delete or rename for the next reboot).
297 # - Calling os.unlink on a file that has been opened with Mercurial's
282 # - Calling os.unlink on a file that has been opened with Mercurial's
298 # posixfile (or comparable methods) will delay the actual deletion of
283 # posixfile (or comparable methods) will delay the actual deletion of
299 # the file for as long as the file is held open. The filename is blocked
284 # the file for as long as the file is held open. The filename is blocked
300 # during that time and cannot be used for recreating a new file under
285 # during that time and cannot be used for recreating a new file under
301 # that same name ("zombie file"). Directories containing such zombie files
286 # that same name ("zombie file"). Directories containing such zombie files
302 # cannot be removed or moved.
287 # cannot be removed or moved.
303 # A file that has been opened with posixfile can be renamed, so we rename
288 # A file that has been opened with posixfile can be renamed, so we rename
304 # f to a random temporary name before calling os.unlink on it. This allows
289 # f to a random temporary name before calling os.unlink on it. This allows
305 # callers to recreate f immediately while having other readers do their
290 # callers to recreate f immediately while having other readers do their
306 # implicit zombie filename blocking on a temporary name.
291 # implicit zombie filename blocking on a temporary name.
307
292
308 for tries in xrange(10):
293 for tries in xrange(10):
309 temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
294 temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
310 try:
295 try:
311 os.rename(f, temp) # raises OSError EEXIST if temp exists
296 os.rename(f, temp) # raises OSError EEXIST if temp exists
312 break
297 break
313 except OSError, e:
298 except OSError, e:
314 if e.errno != errno.EEXIST:
299 if e.errno != errno.EEXIST:
315 raise
300 raise
316 else:
301 else:
317 raise IOError, (errno.EEXIST, "No usable temporary filename found")
302 raise IOError, (errno.EEXIST, "No usable temporary filename found")
318
303
319 try:
304 try:
320 os.unlink(temp)
305 os.unlink(temp)
321 except:
306 except:
322 # Some very rude AV-scanners on Windows may cause this unlink to fail.
307 # Some very rude AV-scanners on Windows may cause this unlink to fail.
323 # Not aborting here just leaks the temp file, whereas aborting at this
308 # Not aborting here just leaks the temp file, whereas aborting at this
324 # point may leave serious inconsistencies. Ideally, we would notify
309 # point may leave serious inconsistencies. Ideally, we would notify
325 # the user in this case here.
310 # the user in this case here.
326 pass
311 pass
327
312
328 def rename(src, dst):
313 def rename(src, dst):
329 '''atomically rename file src to dst, replacing dst if it exists'''
314 '''atomically rename file src to dst, replacing dst if it exists'''
330 try:
315 try:
331 os.rename(src, dst)
316 os.rename(src, dst)
332 except OSError, e:
317 except OSError, e:
333 if e.errno != errno.EEXIST:
318 if e.errno != errno.EEXIST:
334 raise
319 raise
335 unlink(dst)
320 unlink(dst)
336 os.rename(src, dst)
321 os.rename(src, dst)
337
322
338 def spawndetached(args):
339 # No standard library function really spawns a fully detached
340 # process under win32 because they allocate pipes or other objects
341 # to handle standard streams communications. Passing these objects
342 # to the child process requires handle inheritance to be enabled
343 # which makes really detached processes impossible.
344 class STARTUPINFO:
345 dwFlags = subprocess.STARTF_USESHOWWINDOW
346 hStdInput = None
347 hStdOutput = None
348 hStdError = None
349 wShowWindow = subprocess.SW_HIDE
350
351 args = subprocess.list2cmdline(args)
352 # Not running the command in shell mode makes python26 hang when
353 # writing to hgweb output socket.
354 comspec = os.environ.get("COMSPEC", "cmd.exe")
355 args = comspec + " /c " + args
356 hp, ht, pid, tid = subprocess.CreateProcess(
357 None, args,
358 # no special security
359 None, None,
360 # Do not inherit handles
361 0,
362 # DETACHED_PROCESS
363 0x00000008,
364 os.environ,
365 os.getcwd(),
366 STARTUPINFO())
367 return pid
368
369 def gethgcmd():
323 def gethgcmd():
370 return [sys.executable] + sys.argv[:1]
324 return [sys.executable] + sys.argv[:1]
371
325
372 def termwidth():
326 def termwidth():
373 # cmd.exe does not handle CR like a unix console, the CR is
327 # cmd.exe does not handle CR like a unix console, the CR is
374 # counted in the line length. On 80 columns consoles, if 80
328 # counted in the line length. On 80 columns consoles, if 80
375 # characters are written, the following CR won't apply on the
329 # characters are written, the following CR won't apply on the
376 # current line but on the new one. Keep room for it.
330 # current line but on the new one. Keep room for it.
377 return 79
331 return 79
378
332
379 def groupmembers(name):
333 def groupmembers(name):
380 # Don't support groups on Windows for now
334 # Don't support groups on Windows for now
381 raise KeyError()
335 raise KeyError()
382
336
383 try:
337 from win32 import *
384 # override functions with win32 versions if possible
385 from win32 import *
386 except ImportError:
387 pass
388
338
389 expandglobs = True
339 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now