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