##// END OF EJS Templates
shellquote: fix missing quotes for empty string...
Yuya Nishihara -
r24108:d65ecb81 stable
parent child Browse files
Show More
@@ -1,606 +1,606 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 encoding
9 import encoding
10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
10 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
11 import fcntl, re
11 import fcntl, re
12
12
13 posixfile = open
13 posixfile = open
14 normpath = os.path.normpath
14 normpath = os.path.normpath
15 samestat = os.path.samestat
15 samestat = os.path.samestat
16 oslink = os.link
16 oslink = os.link
17 unlink = os.unlink
17 unlink = os.unlink
18 rename = os.rename
18 rename = os.rename
19 expandglobs = False
19 expandglobs = False
20
20
21 umask = os.umask(0)
21 umask = os.umask(0)
22 os.umask(umask)
22 os.umask(umask)
23
23
24 def split(p):
24 def split(p):
25 '''Same as posixpath.split, but faster
25 '''Same as posixpath.split, but faster
26
26
27 >>> import posixpath
27 >>> import posixpath
28 >>> for f in ['/absolute/path/to/file',
28 >>> for f in ['/absolute/path/to/file',
29 ... 'relative/path/to/file',
29 ... 'relative/path/to/file',
30 ... 'file_alone',
30 ... 'file_alone',
31 ... 'path/to/directory/',
31 ... 'path/to/directory/',
32 ... '/multiple/path//separators',
32 ... '/multiple/path//separators',
33 ... '/file_at_root',
33 ... '/file_at_root',
34 ... '///multiple_leading_separators_at_root',
34 ... '///multiple_leading_separators_at_root',
35 ... '']:
35 ... '']:
36 ... assert split(f) == posixpath.split(f), f
36 ... assert split(f) == posixpath.split(f), f
37 '''
37 '''
38 ht = p.rsplit('/', 1)
38 ht = p.rsplit('/', 1)
39 if len(ht) == 1:
39 if len(ht) == 1:
40 return '', p
40 return '', p
41 nh = ht[0].rstrip('/')
41 nh = ht[0].rstrip('/')
42 if nh:
42 if nh:
43 return nh, ht[1]
43 return nh, ht[1]
44 return ht[0] + '/', ht[1]
44 return ht[0] + '/', ht[1]
45
45
46 def openhardlinks():
46 def openhardlinks():
47 '''return true if it is safe to hold open file handles to hardlinks'''
47 '''return true if it is safe to hold open file handles to hardlinks'''
48 return True
48 return True
49
49
50 def nlinks(name):
50 def nlinks(name):
51 '''return number of hardlinks for the given file'''
51 '''return number of hardlinks for the given file'''
52 return os.lstat(name).st_nlink
52 return os.lstat(name).st_nlink
53
53
54 def parsepatchoutput(output_line):
54 def parsepatchoutput(output_line):
55 """parses the output produced by patch and returns the filename"""
55 """parses the output produced by patch and returns the filename"""
56 pf = output_line[14:]
56 pf = output_line[14:]
57 if os.sys.platform == 'OpenVMS':
57 if os.sys.platform == 'OpenVMS':
58 if pf[0] == '`':
58 if pf[0] == '`':
59 pf = pf[1:-1] # Remove the quotes
59 pf = pf[1:-1] # Remove the quotes
60 else:
60 else:
61 if pf.startswith("'") and pf.endswith("'") and " " in pf:
61 if pf.startswith("'") and pf.endswith("'") and " " in pf:
62 pf = pf[1:-1] # Remove the quotes
62 pf = pf[1:-1] # Remove the quotes
63 return pf
63 return pf
64
64
65 def sshargs(sshcmd, host, user, port):
65 def sshargs(sshcmd, host, user, port):
66 '''Build argument list for ssh'''
66 '''Build argument list for ssh'''
67 args = user and ("%s@%s" % (user, host)) or host
67 args = user and ("%s@%s" % (user, host)) or host
68 return port and ("%s -p %s" % (args, port)) or args
68 return port and ("%s -p %s" % (args, port)) or args
69
69
70 def isexec(f):
70 def isexec(f):
71 """check whether a file is executable"""
71 """check whether a file is executable"""
72 return (os.lstat(f).st_mode & 0100 != 0)
72 return (os.lstat(f).st_mode & 0100 != 0)
73
73
74 def setflags(f, l, x):
74 def setflags(f, l, x):
75 s = os.lstat(f).st_mode
75 s = os.lstat(f).st_mode
76 if l:
76 if l:
77 if not stat.S_ISLNK(s):
77 if not stat.S_ISLNK(s):
78 # switch file to link
78 # switch file to link
79 fp = open(f)
79 fp = open(f)
80 data = fp.read()
80 data = fp.read()
81 fp.close()
81 fp.close()
82 os.unlink(f)
82 os.unlink(f)
83 try:
83 try:
84 os.symlink(data, f)
84 os.symlink(data, f)
85 except OSError:
85 except OSError:
86 # failed to make a link, rewrite file
86 # failed to make a link, rewrite file
87 fp = open(f, "w")
87 fp = open(f, "w")
88 fp.write(data)
88 fp.write(data)
89 fp.close()
89 fp.close()
90 # no chmod needed at this point
90 # no chmod needed at this point
91 return
91 return
92 if stat.S_ISLNK(s):
92 if stat.S_ISLNK(s):
93 # switch link to file
93 # switch link to file
94 data = os.readlink(f)
94 data = os.readlink(f)
95 os.unlink(f)
95 os.unlink(f)
96 fp = open(f, "w")
96 fp = open(f, "w")
97 fp.write(data)
97 fp.write(data)
98 fp.close()
98 fp.close()
99 s = 0666 & ~umask # avoid restatting for chmod
99 s = 0666 & ~umask # avoid restatting for chmod
100
100
101 sx = s & 0100
101 sx = s & 0100
102 if x and not sx:
102 if x and not sx:
103 # Turn on +x for every +r bit when making a file executable
103 # Turn on +x for every +r bit when making a file executable
104 # and obey umask.
104 # and obey umask.
105 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
105 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
106 elif not x and sx:
106 elif not x and sx:
107 # Turn off all +x bits
107 # Turn off all +x bits
108 os.chmod(f, s & 0666)
108 os.chmod(f, s & 0666)
109
109
110 def copymode(src, dst, mode=None):
110 def copymode(src, dst, mode=None):
111 '''Copy the file mode from the file at path src to dst.
111 '''Copy the file mode from the file at path src to dst.
112 If src doesn't exist, we're using mode instead. If mode is None, we're
112 If src doesn't exist, we're using mode instead. If mode is None, we're
113 using umask.'''
113 using umask.'''
114 try:
114 try:
115 st_mode = os.lstat(src).st_mode & 0777
115 st_mode = os.lstat(src).st_mode & 0777
116 except OSError, inst:
116 except OSError, inst:
117 if inst.errno != errno.ENOENT:
117 if inst.errno != errno.ENOENT:
118 raise
118 raise
119 st_mode = mode
119 st_mode = mode
120 if st_mode is None:
120 if st_mode is None:
121 st_mode = ~umask
121 st_mode = ~umask
122 st_mode &= 0666
122 st_mode &= 0666
123 os.chmod(dst, st_mode)
123 os.chmod(dst, st_mode)
124
124
125 def checkexec(path):
125 def checkexec(path):
126 """
126 """
127 Check whether the given path is on a filesystem with UNIX-like exec flags
127 Check whether the given path is on a filesystem with UNIX-like exec flags
128
128
129 Requires a directory (like /foo/.hg)
129 Requires a directory (like /foo/.hg)
130 """
130 """
131
131
132 # VFAT on some Linux versions can flip mode but it doesn't persist
132 # VFAT on some Linux versions can flip mode but it doesn't persist
133 # a FS remount. Frequently we can detect it if files are created
133 # a FS remount. Frequently we can detect it if files are created
134 # with exec bit on.
134 # with exec bit on.
135
135
136 try:
136 try:
137 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
137 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
138 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
138 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
139 try:
139 try:
140 os.close(fh)
140 os.close(fh)
141 m = os.stat(fn).st_mode & 0777
141 m = os.stat(fn).st_mode & 0777
142 new_file_has_exec = m & EXECFLAGS
142 new_file_has_exec = m & EXECFLAGS
143 os.chmod(fn, m ^ EXECFLAGS)
143 os.chmod(fn, m ^ EXECFLAGS)
144 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
144 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
145 finally:
145 finally:
146 os.unlink(fn)
146 os.unlink(fn)
147 except (IOError, OSError):
147 except (IOError, OSError):
148 # we don't care, the user probably won't be able to commit anyway
148 # we don't care, the user probably won't be able to commit anyway
149 return False
149 return False
150 return not (new_file_has_exec or exec_flags_cannot_flip)
150 return not (new_file_has_exec or exec_flags_cannot_flip)
151
151
152 def checklink(path):
152 def checklink(path):
153 """check whether the given path is on a symlink-capable filesystem"""
153 """check whether the given path is on a symlink-capable filesystem"""
154 # mktemp is not racy because symlink creation will fail if the
154 # mktemp is not racy because symlink creation will fail if the
155 # file already exists
155 # file already exists
156 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
156 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
157 try:
157 try:
158 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
158 fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
159 try:
159 try:
160 os.symlink(os.path.basename(fd.name), name)
160 os.symlink(os.path.basename(fd.name), name)
161 os.unlink(name)
161 os.unlink(name)
162 return True
162 return True
163 finally:
163 finally:
164 fd.close()
164 fd.close()
165 except AttributeError:
165 except AttributeError:
166 return False
166 return False
167 except OSError, inst:
167 except OSError, inst:
168 # sshfs might report failure while successfully creating the link
168 # sshfs might report failure while successfully creating the link
169 if inst[0] == errno.EIO and os.path.exists(name):
169 if inst[0] == errno.EIO and os.path.exists(name):
170 os.unlink(name)
170 os.unlink(name)
171 return False
171 return False
172
172
173 def checkosfilename(path):
173 def checkosfilename(path):
174 '''Check that the base-relative path is a valid filename on this platform.
174 '''Check that the base-relative path is a valid filename on this platform.
175 Returns None if the path is ok, or a UI string describing the problem.'''
175 Returns None if the path is ok, or a UI string describing the problem.'''
176 pass # on posix platforms, every path is ok
176 pass # on posix platforms, every path is ok
177
177
178 def setbinary(fd):
178 def setbinary(fd):
179 pass
179 pass
180
180
181 def pconvert(path):
181 def pconvert(path):
182 return path
182 return path
183
183
184 def localpath(path):
184 def localpath(path):
185 return path
185 return path
186
186
187 def samefile(fpath1, fpath2):
187 def samefile(fpath1, fpath2):
188 """Returns whether path1 and path2 refer to the same file. This is only
188 """Returns whether path1 and path2 refer to the same file. This is only
189 guaranteed to work for files, not directories."""
189 guaranteed to work for files, not directories."""
190 return os.path.samefile(fpath1, fpath2)
190 return os.path.samefile(fpath1, fpath2)
191
191
192 def samedevice(fpath1, fpath2):
192 def samedevice(fpath1, fpath2):
193 """Returns whether fpath1 and fpath2 are on the same device. This is only
193 """Returns whether fpath1 and fpath2 are on the same device. This is only
194 guaranteed to work for files, not directories."""
194 guaranteed to work for files, not directories."""
195 st1 = os.lstat(fpath1)
195 st1 = os.lstat(fpath1)
196 st2 = os.lstat(fpath2)
196 st2 = os.lstat(fpath2)
197 return st1.st_dev == st2.st_dev
197 return st1.st_dev == st2.st_dev
198
198
199 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
199 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
200 def normcase(path):
200 def normcase(path):
201 return path.lower()
201 return path.lower()
202
202
203 if sys.platform == 'darwin':
203 if sys.platform == 'darwin':
204
204
205 def normcase(path):
205 def normcase(path):
206 '''
206 '''
207 Normalize a filename for OS X-compatible comparison:
207 Normalize a filename for OS X-compatible comparison:
208 - escape-encode invalid characters
208 - escape-encode invalid characters
209 - decompose to NFD
209 - decompose to NFD
210 - lowercase
210 - lowercase
211 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
211 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
212
212
213 >>> normcase('UPPER')
213 >>> normcase('UPPER')
214 'upper'
214 'upper'
215 >>> normcase('Caf\xc3\xa9')
215 >>> normcase('Caf\xc3\xa9')
216 'cafe\\xcc\\x81'
216 'cafe\\xcc\\x81'
217 >>> normcase('\xc3\x89')
217 >>> normcase('\xc3\x89')
218 'e\\xcc\\x81'
218 'e\\xcc\\x81'
219 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
219 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
220 '%b8%ca%c3\\xca\\xbe%c8.jpg'
220 '%b8%ca%c3\\xca\\xbe%c8.jpg'
221 '''
221 '''
222
222
223 try:
223 try:
224 return encoding.asciilower(path) # exception for non-ASCII
224 return encoding.asciilower(path) # exception for non-ASCII
225 except UnicodeDecodeError:
225 except UnicodeDecodeError:
226 pass
226 pass
227 try:
227 try:
228 u = path.decode('utf-8')
228 u = path.decode('utf-8')
229 except UnicodeDecodeError:
229 except UnicodeDecodeError:
230 # OS X percent-encodes any bytes that aren't valid utf-8
230 # OS X percent-encodes any bytes that aren't valid utf-8
231 s = ''
231 s = ''
232 g = ''
232 g = ''
233 l = 0
233 l = 0
234 for c in path:
234 for c in path:
235 o = ord(c)
235 o = ord(c)
236 if l and o < 128 or o >= 192:
236 if l and o < 128 or o >= 192:
237 # we want a continuation byte, but didn't get one
237 # we want a continuation byte, but didn't get one
238 s += ''.join(["%%%02X" % ord(x) for x in g])
238 s += ''.join(["%%%02X" % ord(x) for x in g])
239 g = ''
239 g = ''
240 l = 0
240 l = 0
241 if l == 0 and o < 128:
241 if l == 0 and o < 128:
242 # ascii
242 # ascii
243 s += c
243 s += c
244 elif l == 0 and 194 <= o < 245:
244 elif l == 0 and 194 <= o < 245:
245 # valid leading bytes
245 # valid leading bytes
246 if o < 224:
246 if o < 224:
247 l = 1
247 l = 1
248 elif o < 240:
248 elif o < 240:
249 l = 2
249 l = 2
250 else:
250 else:
251 l = 3
251 l = 3
252 g = c
252 g = c
253 elif l > 0 and 128 <= o < 192:
253 elif l > 0 and 128 <= o < 192:
254 # valid continuations
254 # valid continuations
255 g += c
255 g += c
256 l -= 1
256 l -= 1
257 if not l:
257 if not l:
258 s += g
258 s += g
259 g = ''
259 g = ''
260 else:
260 else:
261 # invalid
261 # invalid
262 s += "%%%02X" % o
262 s += "%%%02X" % o
263
263
264 # any remaining partial characters
264 # any remaining partial characters
265 s += ''.join(["%%%02X" % ord(x) for x in g])
265 s += ''.join(["%%%02X" % ord(x) for x in g])
266 u = s.decode('utf-8')
266 u = s.decode('utf-8')
267
267
268 # Decompose then lowercase (HFS+ technote specifies lower)
268 # Decompose then lowercase (HFS+ technote specifies lower)
269 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
269 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
270 # drop HFS+ ignored characters
270 # drop HFS+ ignored characters
271 return encoding.hfsignoreclean(enc)
271 return encoding.hfsignoreclean(enc)
272
272
273 if sys.platform == 'cygwin':
273 if sys.platform == 'cygwin':
274 # workaround for cygwin, in which mount point part of path is
274 # workaround for cygwin, in which mount point part of path is
275 # treated as case sensitive, even though underlying NTFS is case
275 # treated as case sensitive, even though underlying NTFS is case
276 # insensitive.
276 # insensitive.
277
277
278 # default mount points
278 # default mount points
279 cygwinmountpoints = sorted([
279 cygwinmountpoints = sorted([
280 "/usr/bin",
280 "/usr/bin",
281 "/usr/lib",
281 "/usr/lib",
282 "/cygdrive",
282 "/cygdrive",
283 ], reverse=True)
283 ], reverse=True)
284
284
285 # use upper-ing as normcase as same as NTFS workaround
285 # use upper-ing as normcase as same as NTFS workaround
286 def normcase(path):
286 def normcase(path):
287 pathlen = len(path)
287 pathlen = len(path)
288 if (pathlen == 0) or (path[0] != os.sep):
288 if (pathlen == 0) or (path[0] != os.sep):
289 # treat as relative
289 # treat as relative
290 return encoding.upper(path)
290 return encoding.upper(path)
291
291
292 # to preserve case of mountpoint part
292 # to preserve case of mountpoint part
293 for mp in cygwinmountpoints:
293 for mp in cygwinmountpoints:
294 if not path.startswith(mp):
294 if not path.startswith(mp):
295 continue
295 continue
296
296
297 mplen = len(mp)
297 mplen = len(mp)
298 if mplen == pathlen: # mount point itself
298 if mplen == pathlen: # mount point itself
299 return mp
299 return mp
300 if path[mplen] == os.sep:
300 if path[mplen] == os.sep:
301 return mp + encoding.upper(path[mplen:])
301 return mp + encoding.upper(path[mplen:])
302
302
303 return encoding.upper(path)
303 return encoding.upper(path)
304
304
305 # Cygwin translates native ACLs to POSIX permissions,
305 # Cygwin translates native ACLs to POSIX permissions,
306 # but these translations are not supported by native
306 # but these translations are not supported by native
307 # tools, so the exec bit tends to be set erroneously.
307 # tools, so the exec bit tends to be set erroneously.
308 # Therefore, disable executable bit access on Cygwin.
308 # Therefore, disable executable bit access on Cygwin.
309 def checkexec(path):
309 def checkexec(path):
310 return False
310 return False
311
311
312 # Similarly, Cygwin's symlink emulation is likely to create
312 # Similarly, Cygwin's symlink emulation is likely to create
313 # problems when Mercurial is used from both Cygwin and native
313 # problems when Mercurial is used from both Cygwin and native
314 # Windows, with other native tools, or on shared volumes
314 # Windows, with other native tools, or on shared volumes
315 def checklink(path):
315 def checklink(path):
316 return False
316 return False
317
317
318 _needsshellquote = None
318 _needsshellquote = None
319 def shellquote(s):
319 def shellquote(s):
320 if os.sys.platform == 'OpenVMS':
320 if os.sys.platform == 'OpenVMS':
321 return '"%s"' % s
321 return '"%s"' % s
322 global _needsshellquote
322 global _needsshellquote
323 if _needsshellquote is None:
323 if _needsshellquote is None:
324 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/-]').search
324 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/-]').search
325 if not _needsshellquote(s):
325 if s and not _needsshellquote(s):
326 # "s" shouldn't have to be quoted
326 # "s" shouldn't have to be quoted
327 return s
327 return s
328 else:
328 else:
329 return "'%s'" % s.replace("'", "'\\''")
329 return "'%s'" % s.replace("'", "'\\''")
330
330
331 def quotecommand(cmd):
331 def quotecommand(cmd):
332 return cmd
332 return cmd
333
333
334 def popen(command, mode='r'):
334 def popen(command, mode='r'):
335 return os.popen(command, mode)
335 return os.popen(command, mode)
336
336
337 def testpid(pid):
337 def testpid(pid):
338 '''return False if pid dead, True if running or not sure'''
338 '''return False if pid dead, True if running or not sure'''
339 if os.sys.platform == 'OpenVMS':
339 if os.sys.platform == 'OpenVMS':
340 return True
340 return True
341 try:
341 try:
342 os.kill(pid, 0)
342 os.kill(pid, 0)
343 return True
343 return True
344 except OSError, inst:
344 except OSError, inst:
345 return inst.errno != errno.ESRCH
345 return inst.errno != errno.ESRCH
346
346
347 def explainexit(code):
347 def explainexit(code):
348 """return a 2-tuple (desc, code) describing a subprocess status
348 """return a 2-tuple (desc, code) describing a subprocess status
349 (codes from kill are negative - not os.system/wait encoding)"""
349 (codes from kill are negative - not os.system/wait encoding)"""
350 if code >= 0:
350 if code >= 0:
351 return _("exited with status %d") % code, code
351 return _("exited with status %d") % code, code
352 return _("killed by signal %d") % -code, -code
352 return _("killed by signal %d") % -code, -code
353
353
354 def isowner(st):
354 def isowner(st):
355 """Return True if the stat object st is from the current user."""
355 """Return True if the stat object st is from the current user."""
356 return st.st_uid == os.getuid()
356 return st.st_uid == os.getuid()
357
357
358 def findexe(command):
358 def findexe(command):
359 '''Find executable for command searching like which does.
359 '''Find executable for command searching like which does.
360 If command is a basename then PATH is searched for command.
360 If command is a basename then PATH is searched for command.
361 PATH isn't searched if command is an absolute or relative path.
361 PATH isn't searched if command is an absolute or relative path.
362 If command isn't found None is returned.'''
362 If command isn't found None is returned.'''
363 if sys.platform == 'OpenVMS':
363 if sys.platform == 'OpenVMS':
364 return command
364 return command
365
365
366 def findexisting(executable):
366 def findexisting(executable):
367 'Will return executable if existing file'
367 'Will return executable if existing file'
368 if os.path.isfile(executable) and os.access(executable, os.X_OK):
368 if os.path.isfile(executable) and os.access(executable, os.X_OK):
369 return executable
369 return executable
370 return None
370 return None
371
371
372 if os.sep in command:
372 if os.sep in command:
373 return findexisting(command)
373 return findexisting(command)
374
374
375 if sys.platform == 'plan9':
375 if sys.platform == 'plan9':
376 return findexisting(os.path.join('/bin', command))
376 return findexisting(os.path.join('/bin', command))
377
377
378 for path in os.environ.get('PATH', '').split(os.pathsep):
378 for path in os.environ.get('PATH', '').split(os.pathsep):
379 executable = findexisting(os.path.join(path, command))
379 executable = findexisting(os.path.join(path, command))
380 if executable is not None:
380 if executable is not None:
381 return executable
381 return executable
382 return None
382 return None
383
383
384 def setsignalhandler():
384 def setsignalhandler():
385 pass
385 pass
386
386
387 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
387 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
388
388
389 def statfiles(files):
389 def statfiles(files):
390 '''Stat each file in files. Yield each stat, or None if a file does not
390 '''Stat each file in files. Yield each stat, or None if a file does not
391 exist or has a type we don't care about.'''
391 exist or has a type we don't care about.'''
392 lstat = os.lstat
392 lstat = os.lstat
393 getkind = stat.S_IFMT
393 getkind = stat.S_IFMT
394 for nf in files:
394 for nf in files:
395 try:
395 try:
396 st = lstat(nf)
396 st = lstat(nf)
397 if getkind(st.st_mode) not in _wantedkinds:
397 if getkind(st.st_mode) not in _wantedkinds:
398 st = None
398 st = None
399 except OSError, err:
399 except OSError, err:
400 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
400 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
401 raise
401 raise
402 st = None
402 st = None
403 yield st
403 yield st
404
404
405 def getuser():
405 def getuser():
406 '''return name of current user'''
406 '''return name of current user'''
407 return getpass.getuser()
407 return getpass.getuser()
408
408
409 def username(uid=None):
409 def username(uid=None):
410 """Return the name of the user with the given uid.
410 """Return the name of the user with the given uid.
411
411
412 If uid is None, return the name of the current user."""
412 If uid is None, return the name of the current user."""
413
413
414 if uid is None:
414 if uid is None:
415 uid = os.getuid()
415 uid = os.getuid()
416 try:
416 try:
417 return pwd.getpwuid(uid)[0]
417 return pwd.getpwuid(uid)[0]
418 except KeyError:
418 except KeyError:
419 return str(uid)
419 return str(uid)
420
420
421 def groupname(gid=None):
421 def groupname(gid=None):
422 """Return the name of the group with the given gid.
422 """Return the name of the group with the given gid.
423
423
424 If gid is None, return the name of the current group."""
424 If gid is None, return the name of the current group."""
425
425
426 if gid is None:
426 if gid is None:
427 gid = os.getgid()
427 gid = os.getgid()
428 try:
428 try:
429 return grp.getgrgid(gid)[0]
429 return grp.getgrgid(gid)[0]
430 except KeyError:
430 except KeyError:
431 return str(gid)
431 return str(gid)
432
432
433 def groupmembers(name):
433 def groupmembers(name):
434 """Return the list of members of the group with the given
434 """Return the list of members of the group with the given
435 name, KeyError if the group does not exist.
435 name, KeyError if the group does not exist.
436 """
436 """
437 return list(grp.getgrnam(name).gr_mem)
437 return list(grp.getgrnam(name).gr_mem)
438
438
439 def spawndetached(args):
439 def spawndetached(args):
440 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
440 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
441 args[0], args)
441 args[0], args)
442
442
443 def gethgcmd():
443 def gethgcmd():
444 return sys.argv[:1]
444 return sys.argv[:1]
445
445
446 def termwidth():
446 def termwidth():
447 try:
447 try:
448 import termios, array
448 import termios, array
449 for dev in (sys.stderr, sys.stdout, sys.stdin):
449 for dev in (sys.stderr, sys.stdout, sys.stdin):
450 try:
450 try:
451 try:
451 try:
452 fd = dev.fileno()
452 fd = dev.fileno()
453 except AttributeError:
453 except AttributeError:
454 continue
454 continue
455 if not os.isatty(fd):
455 if not os.isatty(fd):
456 continue
456 continue
457 try:
457 try:
458 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
458 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
459 width = array.array('h', arri)[1]
459 width = array.array('h', arri)[1]
460 if width > 0:
460 if width > 0:
461 return width
461 return width
462 except AttributeError:
462 except AttributeError:
463 pass
463 pass
464 except ValueError:
464 except ValueError:
465 pass
465 pass
466 except IOError, e:
466 except IOError, e:
467 if e[0] == errno.EINVAL:
467 if e[0] == errno.EINVAL:
468 pass
468 pass
469 else:
469 else:
470 raise
470 raise
471 except ImportError:
471 except ImportError:
472 pass
472 pass
473 return 80
473 return 80
474
474
475 def makedir(path, notindexed):
475 def makedir(path, notindexed):
476 os.mkdir(path)
476 os.mkdir(path)
477
477
478 def unlinkpath(f, ignoremissing=False):
478 def unlinkpath(f, ignoremissing=False):
479 """unlink and remove the directory if it is empty"""
479 """unlink and remove the directory if it is empty"""
480 try:
480 try:
481 os.unlink(f)
481 os.unlink(f)
482 except OSError, e:
482 except OSError, e:
483 if not (ignoremissing and e.errno == errno.ENOENT):
483 if not (ignoremissing and e.errno == errno.ENOENT):
484 raise
484 raise
485 # try removing directories that might now be empty
485 # try removing directories that might now be empty
486 try:
486 try:
487 os.removedirs(os.path.dirname(f))
487 os.removedirs(os.path.dirname(f))
488 except OSError:
488 except OSError:
489 pass
489 pass
490
490
491 def lookupreg(key, name=None, scope=None):
491 def lookupreg(key, name=None, scope=None):
492 return None
492 return None
493
493
494 def hidewindow():
494 def hidewindow():
495 """Hide current shell window.
495 """Hide current shell window.
496
496
497 Used to hide the window opened when starting asynchronous
497 Used to hide the window opened when starting asynchronous
498 child process under Windows, unneeded on other systems.
498 child process under Windows, unneeded on other systems.
499 """
499 """
500 pass
500 pass
501
501
502 class cachestat(object):
502 class cachestat(object):
503 def __init__(self, path):
503 def __init__(self, path):
504 self.stat = os.stat(path)
504 self.stat = os.stat(path)
505
505
506 def cacheable(self):
506 def cacheable(self):
507 return bool(self.stat.st_ino)
507 return bool(self.stat.st_ino)
508
508
509 __hash__ = object.__hash__
509 __hash__ = object.__hash__
510
510
511 def __eq__(self, other):
511 def __eq__(self, other):
512 try:
512 try:
513 # Only dev, ino, size, mtime and atime are likely to change. Out
513 # Only dev, ino, size, mtime and atime are likely to change. Out
514 # of these, we shouldn't compare atime but should compare the
514 # of these, we shouldn't compare atime but should compare the
515 # rest. However, one of the other fields changing indicates
515 # rest. However, one of the other fields changing indicates
516 # something fishy going on, so return False if anything but atime
516 # something fishy going on, so return False if anything but atime
517 # changes.
517 # changes.
518 return (self.stat.st_mode == other.stat.st_mode and
518 return (self.stat.st_mode == other.stat.st_mode and
519 self.stat.st_ino == other.stat.st_ino and
519 self.stat.st_ino == other.stat.st_ino and
520 self.stat.st_dev == other.stat.st_dev and
520 self.stat.st_dev == other.stat.st_dev and
521 self.stat.st_nlink == other.stat.st_nlink and
521 self.stat.st_nlink == other.stat.st_nlink and
522 self.stat.st_uid == other.stat.st_uid and
522 self.stat.st_uid == other.stat.st_uid and
523 self.stat.st_gid == other.stat.st_gid and
523 self.stat.st_gid == other.stat.st_gid and
524 self.stat.st_size == other.stat.st_size and
524 self.stat.st_size == other.stat.st_size and
525 self.stat.st_mtime == other.stat.st_mtime and
525 self.stat.st_mtime == other.stat.st_mtime and
526 self.stat.st_ctime == other.stat.st_ctime)
526 self.stat.st_ctime == other.stat.st_ctime)
527 except AttributeError:
527 except AttributeError:
528 return False
528 return False
529
529
530 def __ne__(self, other):
530 def __ne__(self, other):
531 return not self == other
531 return not self == other
532
532
533 def executablepath():
533 def executablepath():
534 return None # available on Windows only
534 return None # available on Windows only
535
535
536 class unixdomainserver(socket.socket):
536 class unixdomainserver(socket.socket):
537 def __init__(self, join, subsystem):
537 def __init__(self, join, subsystem):
538 '''Create a unix domain socket with the given prefix.'''
538 '''Create a unix domain socket with the given prefix.'''
539 super(unixdomainserver, self).__init__(socket.AF_UNIX)
539 super(unixdomainserver, self).__init__(socket.AF_UNIX)
540 sockname = subsystem + '.sock'
540 sockname = subsystem + '.sock'
541 self.realpath = self.path = join(sockname)
541 self.realpath = self.path = join(sockname)
542 if os.path.islink(self.path):
542 if os.path.islink(self.path):
543 if os.path.exists(self.path):
543 if os.path.exists(self.path):
544 self.realpath = os.readlink(self.path)
544 self.realpath = os.readlink(self.path)
545 else:
545 else:
546 os.unlink(self.path)
546 os.unlink(self.path)
547 try:
547 try:
548 self.bind(self.realpath)
548 self.bind(self.realpath)
549 except socket.error, err:
549 except socket.error, err:
550 if err.args[0] == 'AF_UNIX path too long':
550 if err.args[0] == 'AF_UNIX path too long':
551 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
551 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
552 self.realpath = os.path.join(tmpdir, sockname)
552 self.realpath = os.path.join(tmpdir, sockname)
553 try:
553 try:
554 self.bind(self.realpath)
554 self.bind(self.realpath)
555 os.symlink(self.realpath, self.path)
555 os.symlink(self.realpath, self.path)
556 except (OSError, socket.error):
556 except (OSError, socket.error):
557 self.cleanup()
557 self.cleanup()
558 raise
558 raise
559 else:
559 else:
560 raise
560 raise
561 self.listen(5)
561 self.listen(5)
562
562
563 def cleanup(self):
563 def cleanup(self):
564 def okayifmissing(f, path):
564 def okayifmissing(f, path):
565 try:
565 try:
566 f(path)
566 f(path)
567 except OSError, err:
567 except OSError, err:
568 if err.errno != errno.ENOENT:
568 if err.errno != errno.ENOENT:
569 raise
569 raise
570
570
571 okayifmissing(os.unlink, self.path)
571 okayifmissing(os.unlink, self.path)
572 if self.realpath != self.path:
572 if self.realpath != self.path:
573 okayifmissing(os.unlink, self.realpath)
573 okayifmissing(os.unlink, self.realpath)
574 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
574 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
575
575
576 def statislink(st):
576 def statislink(st):
577 '''check whether a stat result is a symlink'''
577 '''check whether a stat result is a symlink'''
578 return st and stat.S_ISLNK(st.st_mode)
578 return st and stat.S_ISLNK(st.st_mode)
579
579
580 def statisexec(st):
580 def statisexec(st):
581 '''check whether a stat result is an executable file'''
581 '''check whether a stat result is an executable file'''
582 return st and (st.st_mode & 0100 != 0)
582 return st and (st.st_mode & 0100 != 0)
583
583
584 def readpipe(pipe):
584 def readpipe(pipe):
585 """Read all available data from a pipe."""
585 """Read all available data from a pipe."""
586 # We can't fstat() a pipe because Linux will always report 0.
586 # We can't fstat() a pipe because Linux will always report 0.
587 # So, we set the pipe to non-blocking mode and read everything
587 # So, we set the pipe to non-blocking mode and read everything
588 # that's available.
588 # that's available.
589 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
589 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
590 flags |= os.O_NONBLOCK
590 flags |= os.O_NONBLOCK
591 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
591 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
592
592
593 try:
593 try:
594 chunks = []
594 chunks = []
595 while True:
595 while True:
596 try:
596 try:
597 s = pipe.read()
597 s = pipe.read()
598 if not s:
598 if not s:
599 break
599 break
600 chunks.append(s)
600 chunks.append(s)
601 except IOError:
601 except IOError:
602 break
602 break
603
603
604 return ''.join(chunks)
604 return ''.join(chunks)
605 finally:
605 finally:
606 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
606 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
@@ -1,363 +1,363 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, encoding
9 import osutil, encoding
10 import errno, msvcrt, os, re, stat, sys, _winreg
10 import errno, msvcrt, os, re, stat, sys, _winreg
11
11
12 import win32
12 import win32
13 executablepath = win32.executablepath
13 executablepath = win32.executablepath
14 getuser = win32.getuser
14 getuser = win32.getuser
15 hidewindow = win32.hidewindow
15 hidewindow = win32.hidewindow
16 makedir = win32.makedir
16 makedir = win32.makedir
17 nlinks = win32.nlinks
17 nlinks = win32.nlinks
18 oslink = win32.oslink
18 oslink = win32.oslink
19 samedevice = win32.samedevice
19 samedevice = win32.samedevice
20 samefile = win32.samefile
20 samefile = win32.samefile
21 setsignalhandler = win32.setsignalhandler
21 setsignalhandler = win32.setsignalhandler
22 spawndetached = win32.spawndetached
22 spawndetached = win32.spawndetached
23 split = os.path.split
23 split = os.path.split
24 termwidth = win32.termwidth
24 termwidth = win32.termwidth
25 testpid = win32.testpid
25 testpid = win32.testpid
26 unlink = win32.unlink
26 unlink = win32.unlink
27
27
28 umask = 0022
28 umask = 0022
29
29
30 # wrap osutil.posixfile to provide friendlier exceptions
30 # wrap osutil.posixfile to provide friendlier exceptions
31 def posixfile(name, mode='r', buffering=-1):
31 def posixfile(name, mode='r', buffering=-1):
32 try:
32 try:
33 return osutil.posixfile(name, mode, buffering)
33 return osutil.posixfile(name, mode, buffering)
34 except WindowsError, err:
34 except WindowsError, err:
35 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
35 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
36 posixfile.__doc__ = osutil.posixfile.__doc__
36 posixfile.__doc__ = osutil.posixfile.__doc__
37
37
38 class winstdout(object):
38 class winstdout(object):
39 '''stdout on windows misbehaves if sent through a pipe'''
39 '''stdout on windows misbehaves if sent through a pipe'''
40
40
41 def __init__(self, fp):
41 def __init__(self, fp):
42 self.fp = fp
42 self.fp = fp
43
43
44 def __getattr__(self, key):
44 def __getattr__(self, key):
45 return getattr(self.fp, key)
45 return getattr(self.fp, key)
46
46
47 def close(self):
47 def close(self):
48 try:
48 try:
49 self.fp.close()
49 self.fp.close()
50 except IOError:
50 except IOError:
51 pass
51 pass
52
52
53 def write(self, s):
53 def write(self, s):
54 try:
54 try:
55 # This is workaround for "Not enough space" error on
55 # This is workaround for "Not enough space" error on
56 # writing large size of data to console.
56 # writing large size of data to console.
57 limit = 16000
57 limit = 16000
58 l = len(s)
58 l = len(s)
59 start = 0
59 start = 0
60 self.softspace = 0
60 self.softspace = 0
61 while start < l:
61 while start < l:
62 end = start + limit
62 end = start + limit
63 self.fp.write(s[start:end])
63 self.fp.write(s[start:end])
64 start = end
64 start = end
65 except IOError, inst:
65 except IOError, inst:
66 if inst.errno != 0:
66 if inst.errno != 0:
67 raise
67 raise
68 self.close()
68 self.close()
69 raise IOError(errno.EPIPE, 'Broken pipe')
69 raise IOError(errno.EPIPE, 'Broken pipe')
70
70
71 def flush(self):
71 def flush(self):
72 try:
72 try:
73 return self.fp.flush()
73 return self.fp.flush()
74 except IOError, inst:
74 except IOError, inst:
75 if inst.errno != errno.EINVAL:
75 if inst.errno != errno.EINVAL:
76 raise
76 raise
77 self.close()
77 self.close()
78 raise IOError(errno.EPIPE, 'Broken pipe')
78 raise IOError(errno.EPIPE, 'Broken pipe')
79
79
80 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
80 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
81
81
82 def _is_win_9x():
82 def _is_win_9x():
83 '''return true if run on windows 95, 98 or me.'''
83 '''return true if run on windows 95, 98 or me.'''
84 try:
84 try:
85 return sys.getwindowsversion()[3] == 1
85 return sys.getwindowsversion()[3] == 1
86 except AttributeError:
86 except AttributeError:
87 return 'command' in os.environ.get('comspec', '')
87 return 'command' in os.environ.get('comspec', '')
88
88
89 def openhardlinks():
89 def openhardlinks():
90 return not _is_win_9x()
90 return not _is_win_9x()
91
91
92 def parsepatchoutput(output_line):
92 def parsepatchoutput(output_line):
93 """parses the output produced by patch and returns the filename"""
93 """parses the output produced by patch and returns the filename"""
94 pf = output_line[14:]
94 pf = output_line[14:]
95 if pf[0] == '`':
95 if pf[0] == '`':
96 pf = pf[1:-1] # Remove the quotes
96 pf = pf[1:-1] # Remove the quotes
97 return pf
97 return pf
98
98
99 def sshargs(sshcmd, host, user, port):
99 def sshargs(sshcmd, host, user, port):
100 '''Build argument list for ssh or Plink'''
100 '''Build argument list for ssh or Plink'''
101 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
101 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
102 args = user and ("%s@%s" % (user, host)) or host
102 args = user and ("%s@%s" % (user, host)) or host
103 return port and ("%s %s %s" % (args, pflag, port)) or args
103 return port and ("%s %s %s" % (args, pflag, port)) or args
104
104
105 def setflags(f, l, x):
105 def setflags(f, l, x):
106 pass
106 pass
107
107
108 def copymode(src, dst, mode=None):
108 def copymode(src, dst, mode=None):
109 pass
109 pass
110
110
111 def checkexec(path):
111 def checkexec(path):
112 return False
112 return False
113
113
114 def checklink(path):
114 def checklink(path):
115 return False
115 return False
116
116
117 def setbinary(fd):
117 def setbinary(fd):
118 # When run without console, pipes may expose invalid
118 # When run without console, pipes may expose invalid
119 # fileno(), usually set to -1.
119 # fileno(), usually set to -1.
120 fno = getattr(fd, 'fileno', None)
120 fno = getattr(fd, 'fileno', None)
121 if fno is not None and fno() >= 0:
121 if fno is not None and fno() >= 0:
122 msvcrt.setmode(fno(), os.O_BINARY)
122 msvcrt.setmode(fno(), os.O_BINARY)
123
123
124 def pconvert(path):
124 def pconvert(path):
125 return path.replace(os.sep, '/')
125 return path.replace(os.sep, '/')
126
126
127 def localpath(path):
127 def localpath(path):
128 return path.replace('/', '\\')
128 return path.replace('/', '\\')
129
129
130 def normpath(path):
130 def normpath(path):
131 return pconvert(os.path.normpath(path))
131 return pconvert(os.path.normpath(path))
132
132
133 def normcase(path):
133 def normcase(path):
134 return encoding.upper(path)
134 return encoding.upper(path)
135
135
136 def samestat(s1, s2):
136 def samestat(s1, s2):
137 return False
137 return False
138
138
139 # 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:
140 # - 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
141 # quoted (i.e. it ends the quoted region)
141 # quoted (i.e. it ends the quoted region)
142 # - 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
143 # - 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
144 # backslash
144 # backslash
145 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
145 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
146 # 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
147 # the number of backslashes that precede double quotes and add another
147 # the number of backslashes that precede double quotes and add another
148 # backslash before every double quote (being careful with the double
148 # backslash before every double quote (being careful with the double
149 # quote we've appended to the end)
149 # quote we've appended to the end)
150 _quotere = None
150 _quotere = None
151 _needsshellquote = None
151 _needsshellquote = None
152 def shellquote(s):
152 def shellquote(s):
153 global _quotere
153 global _quotere
154 if _quotere is None:
154 if _quotere is None:
155 _quotere = re.compile(r'(\\*)("|\\$)')
155 _quotere = re.compile(r'(\\*)("|\\$)')
156 global _needsshellquote
156 global _needsshellquote
157 if _needsshellquote is None:
157 if _needsshellquote is None:
158 # ":" and "\\" are also treated as "safe character", because
158 # ":" and "\\" are also treated as "safe character", because
159 # they are used as a part of path name (and the latter doesn't
159 # they are used as a part of path name (and the latter doesn't
160 # work as "escape character", like one on posix) on Windows
160 # work as "escape character", like one on posix) on Windows
161 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/\\-]').search
161 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/\\-]').search
162 if not _needsshellquote(s) and not _quotere.search(s):
162 if s and not _needsshellquote(s) and not _quotere.search(s):
163 # "s" shouldn't have to be quoted
163 # "s" shouldn't have to be quoted
164 return s
164 return s
165 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
165 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
166
166
167 def quotecommand(cmd):
167 def quotecommand(cmd):
168 """Build a command string suitable for os.popen* calls."""
168 """Build a command string suitable for os.popen* calls."""
169 if sys.version_info < (2, 7, 1):
169 if sys.version_info < (2, 7, 1):
170 # Python versions since 2.7.1 do this extra quoting themselves
170 # Python versions since 2.7.1 do this extra quoting themselves
171 return '"' + cmd + '"'
171 return '"' + cmd + '"'
172 return cmd
172 return cmd
173
173
174 def popen(command, mode='r'):
174 def popen(command, mode='r'):
175 # Work around "popen spawned process may not write to stdout
175 # Work around "popen spawned process may not write to stdout
176 # under windows"
176 # under windows"
177 # http://bugs.python.org/issue1366
177 # http://bugs.python.org/issue1366
178 command += " 2> %s" % os.devnull
178 command += " 2> %s" % os.devnull
179 return os.popen(quotecommand(command), mode)
179 return os.popen(quotecommand(command), mode)
180
180
181 def explainexit(code):
181 def explainexit(code):
182 return _("exited with status %d") % code, code
182 return _("exited with status %d") % code, code
183
183
184 # if you change this stub into a real check, please try to implement the
184 # if you change this stub into a real check, please try to implement the
185 # username and groupname functions above, too.
185 # username and groupname functions above, too.
186 def isowner(st):
186 def isowner(st):
187 return True
187 return True
188
188
189 def findexe(command):
189 def findexe(command):
190 '''Find executable for command searching like cmd.exe does.
190 '''Find executable for command searching like cmd.exe does.
191 If command is a basename then PATH is searched for command.
191 If command is a basename then PATH is searched for command.
192 PATH isn't searched if command is an absolute or relative path.
192 PATH isn't searched if command is an absolute or relative path.
193 An extension from PATHEXT is found and added if not present.
193 An extension from PATHEXT is found and added if not present.
194 If command isn't found None is returned.'''
194 If command isn't found None is returned.'''
195 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
195 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
196 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
196 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
197 if os.path.splitext(command)[1].lower() in pathexts:
197 if os.path.splitext(command)[1].lower() in pathexts:
198 pathexts = ['']
198 pathexts = ['']
199
199
200 def findexisting(pathcommand):
200 def findexisting(pathcommand):
201 'Will append extension (if needed) and return existing file'
201 'Will append extension (if needed) and return existing file'
202 for ext in pathexts:
202 for ext in pathexts:
203 executable = pathcommand + ext
203 executable = pathcommand + ext
204 if os.path.exists(executable):
204 if os.path.exists(executable):
205 return executable
205 return executable
206 return None
206 return None
207
207
208 if os.sep in command:
208 if os.sep in command:
209 return findexisting(command)
209 return findexisting(command)
210
210
211 for path in os.environ.get('PATH', '').split(os.pathsep):
211 for path in os.environ.get('PATH', '').split(os.pathsep):
212 executable = findexisting(os.path.join(path, command))
212 executable = findexisting(os.path.join(path, command))
213 if executable is not None:
213 if executable is not None:
214 return executable
214 return executable
215 return findexisting(os.path.expanduser(os.path.expandvars(command)))
215 return findexisting(os.path.expanduser(os.path.expandvars(command)))
216
216
217 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
217 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
218
218
219 def statfiles(files):
219 def statfiles(files):
220 '''Stat each file in files. Yield each stat, or None if a file
220 '''Stat each file in files. Yield each stat, or None if a file
221 does not exist or has a type we don't care about.
221 does not exist or has a type we don't care about.
222
222
223 Cluster and cache stat per directory to minimize number of OS stat calls.'''
223 Cluster and cache stat per directory to minimize number of OS stat calls.'''
224 dircache = {} # dirname -> filename -> status | None if file does not exist
224 dircache = {} # dirname -> filename -> status | None if file does not exist
225 getkind = stat.S_IFMT
225 getkind = stat.S_IFMT
226 for nf in files:
226 for nf in files:
227 nf = normcase(nf)
227 nf = normcase(nf)
228 dir, base = os.path.split(nf)
228 dir, base = os.path.split(nf)
229 if not dir:
229 if not dir:
230 dir = '.'
230 dir = '.'
231 cache = dircache.get(dir, None)
231 cache = dircache.get(dir, None)
232 if cache is None:
232 if cache is None:
233 try:
233 try:
234 dmap = dict([(normcase(n), s)
234 dmap = dict([(normcase(n), s)
235 for n, k, s in osutil.listdir(dir, True)
235 for n, k, s in osutil.listdir(dir, True)
236 if getkind(s.st_mode) in _wantedkinds])
236 if getkind(s.st_mode) in _wantedkinds])
237 except OSError, err:
237 except OSError, err:
238 # handle directory not found in Python version prior to 2.5
238 # handle directory not found in Python version prior to 2.5
239 # Python <= 2.4 returns native Windows code 3 in errno
239 # Python <= 2.4 returns native Windows code 3 in errno
240 # Python >= 2.5 returns ENOENT and adds winerror field
240 # Python >= 2.5 returns ENOENT and adds winerror field
241 # EINVAL is raised if dir is not a directory.
241 # EINVAL is raised if dir is not a directory.
242 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
242 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
243 errno.ENOTDIR):
243 errno.ENOTDIR):
244 raise
244 raise
245 dmap = {}
245 dmap = {}
246 cache = dircache.setdefault(dir, dmap)
246 cache = dircache.setdefault(dir, dmap)
247 yield cache.get(base, None)
247 yield cache.get(base, None)
248
248
249 def username(uid=None):
249 def username(uid=None):
250 """Return the name of the user with the given uid.
250 """Return the name of the user with the given uid.
251
251
252 If uid is None, return the name of the current user."""
252 If uid is None, return the name of the current user."""
253 return None
253 return None
254
254
255 def groupname(gid=None):
255 def groupname(gid=None):
256 """Return the name of the group with the given gid.
256 """Return the name of the group with the given gid.
257
257
258 If gid is None, return the name of the current group."""
258 If gid is None, return the name of the current group."""
259 return None
259 return None
260
260
261 def _removedirs(name):
261 def _removedirs(name):
262 """special version of os.removedirs that does not remove symlinked
262 """special version of os.removedirs that does not remove symlinked
263 directories or junction points if they actually contain files"""
263 directories or junction points if they actually contain files"""
264 if osutil.listdir(name):
264 if osutil.listdir(name):
265 return
265 return
266 os.rmdir(name)
266 os.rmdir(name)
267 head, tail = os.path.split(name)
267 head, tail = os.path.split(name)
268 if not tail:
268 if not tail:
269 head, tail = os.path.split(head)
269 head, tail = os.path.split(head)
270 while head and tail:
270 while head and tail:
271 try:
271 try:
272 if osutil.listdir(head):
272 if osutil.listdir(head):
273 return
273 return
274 os.rmdir(head)
274 os.rmdir(head)
275 except (ValueError, OSError):
275 except (ValueError, OSError):
276 break
276 break
277 head, tail = os.path.split(head)
277 head, tail = os.path.split(head)
278
278
279 def unlinkpath(f, ignoremissing=False):
279 def unlinkpath(f, ignoremissing=False):
280 """unlink and remove the directory if it is empty"""
280 """unlink and remove the directory if it is empty"""
281 try:
281 try:
282 unlink(f)
282 unlink(f)
283 except OSError, e:
283 except OSError, e:
284 if not (ignoremissing and e.errno == errno.ENOENT):
284 if not (ignoremissing and e.errno == errno.ENOENT):
285 raise
285 raise
286 # try removing directories that might now be empty
286 # try removing directories that might now be empty
287 try:
287 try:
288 _removedirs(os.path.dirname(f))
288 _removedirs(os.path.dirname(f))
289 except OSError:
289 except OSError:
290 pass
290 pass
291
291
292 def rename(src, dst):
292 def rename(src, dst):
293 '''atomically rename file src to dst, replacing dst if it exists'''
293 '''atomically rename file src to dst, replacing dst if it exists'''
294 try:
294 try:
295 os.rename(src, dst)
295 os.rename(src, dst)
296 except OSError, e:
296 except OSError, e:
297 if e.errno != errno.EEXIST:
297 if e.errno != errno.EEXIST:
298 raise
298 raise
299 unlink(dst)
299 unlink(dst)
300 os.rename(src, dst)
300 os.rename(src, dst)
301
301
302 def gethgcmd():
302 def gethgcmd():
303 return [sys.executable] + sys.argv[:1]
303 return [sys.executable] + sys.argv[:1]
304
304
305 def groupmembers(name):
305 def groupmembers(name):
306 # Don't support groups on Windows for now
306 # Don't support groups on Windows for now
307 raise KeyError
307 raise KeyError
308
308
309 def isexec(f):
309 def isexec(f):
310 return False
310 return False
311
311
312 class cachestat(object):
312 class cachestat(object):
313 def __init__(self, path):
313 def __init__(self, path):
314 pass
314 pass
315
315
316 def cacheable(self):
316 def cacheable(self):
317 return False
317 return False
318
318
319 def lookupreg(key, valname=None, scope=None):
319 def lookupreg(key, valname=None, scope=None):
320 ''' Look up a key/value name in the Windows registry.
320 ''' Look up a key/value name in the Windows registry.
321
321
322 valname: value name. If unspecified, the default value for the key
322 valname: value name. If unspecified, the default value for the key
323 is used.
323 is used.
324 scope: optionally specify scope for registry lookup, this can be
324 scope: optionally specify scope for registry lookup, this can be
325 a sequence of scopes to look up in order. Default (CURRENT_USER,
325 a sequence of scopes to look up in order. Default (CURRENT_USER,
326 LOCAL_MACHINE).
326 LOCAL_MACHINE).
327 '''
327 '''
328 if scope is None:
328 if scope is None:
329 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
329 scope = (_winreg.HKEY_CURRENT_USER, _winreg.HKEY_LOCAL_MACHINE)
330 elif not isinstance(scope, (list, tuple)):
330 elif not isinstance(scope, (list, tuple)):
331 scope = (scope,)
331 scope = (scope,)
332 for s in scope:
332 for s in scope:
333 try:
333 try:
334 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
334 val = _winreg.QueryValueEx(_winreg.OpenKey(s, key), valname)[0]
335 # never let a Unicode string escape into the wild
335 # never let a Unicode string escape into the wild
336 return encoding.tolocal(val.encode('UTF-8'))
336 return encoding.tolocal(val.encode('UTF-8'))
337 except EnvironmentError:
337 except EnvironmentError:
338 pass
338 pass
339
339
340 expandglobs = True
340 expandglobs = True
341
341
342 def statislink(st):
342 def statislink(st):
343 '''check whether a stat result is a symlink'''
343 '''check whether a stat result is a symlink'''
344 return False
344 return False
345
345
346 def statisexec(st):
346 def statisexec(st):
347 '''check whether a stat result is an executable file'''
347 '''check whether a stat result is an executable file'''
348 return False
348 return False
349
349
350 def readpipe(pipe):
350 def readpipe(pipe):
351 """Read all available data from a pipe."""
351 """Read all available data from a pipe."""
352 chunks = []
352 chunks = []
353 while True:
353 while True:
354 size = os.fstat(pipe.fileno()).st_size
354 size = os.fstat(pipe.fileno()).st_size
355 if not size:
355 if not size:
356 break
356 break
357
357
358 s = pipe.read(size)
358 s = pipe.read(size)
359 if not s:
359 if not s:
360 break
360 break
361 chunks.append(s)
361 chunks.append(s)
362
362
363 return ''.join(chunks)
363 return ''.join(chunks)
@@ -1,324 +1,340 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
2 $ echo "extdiff=" >> $HGRCPATH
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ echo b > b
7 $ echo b > b
8 $ hg add
8 $ hg add
9 adding a
9 adding a
10 adding b
10 adding b
11
11
12 Should diff cloned directories:
12 Should diff cloned directories:
13
13
14 $ hg extdiff -o -r $opt
14 $ hg extdiff -o -r $opt
15 Only in a: a
15 Only in a: a
16 Only in a: b
16 Only in a: b
17 [1]
17 [1]
18
18
19 $ cat <<EOF >> $HGRCPATH
19 $ cat <<EOF >> $HGRCPATH
20 > [extdiff]
20 > [extdiff]
21 > cmd.falabala = echo
21 > cmd.falabala = echo
22 > opts.falabala = diffing
22 > opts.falabala = diffing
23 > cmd.edspace = echo
23 > cmd.edspace = echo
24 > opts.edspace = "name <user@example.com>"
24 > opts.edspace = "name <user@example.com>"
25 > EOF
25 > EOF
26
26
27 $ hg falabala
27 $ hg falabala
28 diffing a.000000000000 a
28 diffing a.000000000000 a
29 [1]
29 [1]
30
30
31 $ hg help falabala
31 $ hg help falabala
32 hg falabala [OPTION]... [FILE]...
32 hg falabala [OPTION]... [FILE]...
33
33
34 use 'echo' to diff repository (or selected files)
34 use 'echo' to diff repository (or selected files)
35
35
36 Show differences between revisions for the specified files, using the
36 Show differences between revisions for the specified files, using the
37 'echo' program.
37 'echo' program.
38
38
39 When two revision arguments are given, then changes are shown between
39 When two revision arguments are given, then changes are shown between
40 those revisions. If only one revision is specified then that revision is
40 those revisions. If only one revision is specified then that revision is
41 compared to the working directory, and, when no revisions are specified,
41 compared to the working directory, and, when no revisions are specified,
42 the working directory files are compared to its parent.
42 the working directory files are compared to its parent.
43
43
44 options ([+] can be repeated):
44 options ([+] can be repeated):
45
45
46 -o --option OPT [+] pass option to comparison program
46 -o --option OPT [+] pass option to comparison program
47 -r --rev REV [+] revision
47 -r --rev REV [+] revision
48 -c --change REV change made by revision
48 -c --change REV change made by revision
49 -I --include PATTERN [+] include names matching the given patterns
49 -I --include PATTERN [+] include names matching the given patterns
50 -X --exclude PATTERN [+] exclude names matching the given patterns
50 -X --exclude PATTERN [+] exclude names matching the given patterns
51
51
52 (some details hidden, use --verbose to show complete help)
52 (some details hidden, use --verbose to show complete help)
53
53
54 $ hg ci -d '0 0' -mtest1
54 $ hg ci -d '0 0' -mtest1
55
55
56 $ echo b >> a
56 $ echo b >> a
57 $ hg ci -d '1 0' -mtest2
57 $ hg ci -d '1 0' -mtest2
58
58
59 Should diff cloned files directly:
59 Should diff cloned files directly:
60
60
61 $ hg falabala -r 0:1
61 $ hg falabala -r 0:1
62 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
62 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
63 [1]
63 [1]
64
64
65 Test diff during merge:
65 Test diff during merge:
66
66
67 $ hg update -C 0
67 $ hg update -C 0
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 $ echo c >> c
69 $ echo c >> c
70 $ hg add c
70 $ hg add c
71 $ hg ci -m "new branch" -d '1 0'
71 $ hg ci -m "new branch" -d '1 0'
72 created new head
72 created new head
73 $ hg merge 1
73 $ hg merge 1
74 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 (branch merge, don't forget to commit)
75 (branch merge, don't forget to commit)
76
76
77 Should diff cloned file against wc file:
77 Should diff cloned file against wc file:
78
78
79 $ hg falabala
79 $ hg falabala
80 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob)
80 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob)
81 [1]
81 [1]
82
82
83
83
84 Test --change option:
84 Test --change option:
85
85
86 $ hg ci -d '2 0' -mtest3
86 $ hg ci -d '2 0' -mtest3
87 $ hg falabala -c 1
87 $ hg falabala -c 1
88 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
88 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
89 [1]
89 [1]
90
90
91 Check diff are made from the first parent:
91 Check diff are made from the first parent:
92
92
93 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
93 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
94 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob)
94 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob)
95 diff-like tools yield a non-zero exit code
95 diff-like tools yield a non-zero exit code
96
96
97 issue4463: usage of command line configuration without additional quoting
97 issue4463: usage of command line configuration without additional quoting
98
98
99 $ cat <<EOF >> $HGRCPATH
99 $ cat <<EOF >> $HGRCPATH
100 > [extdiff]
100 > [extdiff]
101 > cmd.4463a = echo
101 > cmd.4463a = echo
102 > opts.4463a = a-naked 'single quoted' "double quoted"
102 > opts.4463a = a-naked 'single quoted' "double quoted"
103 > 4463b = echo b-naked 'single quoted' "double quoted"
103 > 4463b = echo b-naked 'single quoted' "double quoted"
104 > echo =
104 > echo =
105 > EOF
105 > EOF
106 $ hg update -q -C 0
106 $ hg update -q -C 0
107 $ echo a >> a
107 $ echo a >> a
108 #if windows
108 #if windows
109 $ hg --debug 4463a | grep '^running'
109 $ hg --debug 4463a | grep '^running'
110 running 'echo a-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
110 running 'echo a-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
111 $ hg --debug 4463b | grep '^running'
111 $ hg --debug 4463b | grep '^running'
112 running 'echo b-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
112 running 'echo b-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
113 $ hg --debug echo | grep '^running'
113 $ hg --debug echo | grep '^running'
114 running '*echo* *\\a *\\a' in */extdiff.* (glob)
114 running '*echo* *\\a *\\a' in */extdiff.* (glob)
115 #else
115 #else
116 $ hg --debug 4463a | grep '^running'
116 $ hg --debug 4463a | grep '^running'
117 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
117 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
118 $ hg --debug 4463b | grep '^running'
118 $ hg --debug 4463b | grep '^running'
119 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
119 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
120 $ hg --debug echo | grep '^running'
120 $ hg --debug echo | grep '^running'
121 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob)
121 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob)
122 #endif
122 #endif
123
123
124 (getting options from other than extdiff section)
124 (getting options from other than extdiff section)
125
125
126 $ cat <<EOF >> $HGRCPATH
126 $ cat <<EOF >> $HGRCPATH
127 > [extdiff]
127 > [extdiff]
128 > # using diff-tools diffargs
128 > # using diff-tools diffargs
129 > 4463b2 = echo
129 > 4463b2 = echo
130 > # using merge-tools diffargs
130 > # using merge-tools diffargs
131 > 4463b3 = echo
131 > 4463b3 = echo
132 > # no diffargs
132 > # no diffargs
133 > 4463b4 = echo
133 > 4463b4 = echo
134 > [diff-tools]
134 > [diff-tools]
135 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
135 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
136 > [merge-tools]
136 > [merge-tools]
137 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
137 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
138 > EOF
138 > EOF
139 #if windows
139 #if windows
140 $ hg --debug 4463b2 | grep '^running'
140 $ hg --debug 4463b2 | grep '^running'
141 running 'echo b2-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
141 running 'echo b2-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
142 $ hg --debug 4463b3 | grep '^running'
142 $ hg --debug 4463b3 | grep '^running'
143 running 'echo b3-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
143 running 'echo b3-naked \'single quoted\' "double quoted" *\\a *\\a' in */extdiff.* (glob)
144 $ hg --debug 4463b4 | grep '^running'
144 $ hg --debug 4463b4 | grep '^running'
145 running 'echo *\\a *\\a' in */extdiff.* (glob)
145 running 'echo *\\a *\\a' in */extdiff.* (glob)
146 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
146 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
147 running 'echo b4-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
147 running 'echo b4-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
148 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
148 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
149 running 'echo echo-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
149 running 'echo echo-naked "being quoted" *\\a *\\a' in */extdiff.* (glob)
150 #else
150 #else
151 $ hg --debug 4463b2 | grep '^running'
151 $ hg --debug 4463b2 | grep '^running'
152 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
152 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
153 $ hg --debug 4463b3 | grep '^running'
153 $ hg --debug 4463b3 | grep '^running'
154 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
154 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob)
155 $ hg --debug 4463b4 | grep '^running'
155 $ hg --debug 4463b4 | grep '^running'
156 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob)
156 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob)
157 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
157 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
158 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
158 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
159 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
159 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
160 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
160 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob)
161 #endif
161 #endif
162
162
163 $ touch 'sp ace'
163 $ touch 'sp ace'
164 $ hg add 'sp ace'
164 $ hg add 'sp ace'
165 $ hg ci -m 'sp ace'
165 $ hg ci -m 'sp ace'
166 created new head
166 created new head
167 $ echo > 'sp ace'
167 $ echo > 'sp ace'
168
168
169 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
169 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
170
170
171 $ cat <<EOF >> $HGRCPATH
171 $ cat <<EOF >> $HGRCPATH
172 > [extdiff]
172 > [extdiff]
173 > odd =
173 > odd =
174 > [merge-tools]
174 > [merge-tools]
175 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
175 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
176 > odd.executable = echo
176 > odd.executable = echo
177 > EOF
177 > EOF
178 #if windows
178 #if windows
179 TODO
179 TODO
180 #else
180 #else
181 $ hg --debug odd | grep '^running'
181 $ hg --debug odd | grep '^running'
182 running "*/bin/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob)
182 running "*/bin/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob)
183 #endif
183 #endif
184
184
185 Empty argument must be quoted
186
187 $ cat <<EOF >> $HGRCPATH
188 > [extdiff]
189 > kdiff3 = echo
190 > [merge-tools]
191 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
192 > EOF
193 #if windows
194 $ hg --debug kdiff3 -r0 | grep '^running'
195 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob)
196 #else
197 $ hg --debug kdiff3 -r0 | grep '^running'
198 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob)
199 #endif
200
185 #if execbit
201 #if execbit
186
202
187 Test extdiff of multiple files in tmp dir:
203 Test extdiff of multiple files in tmp dir:
188
204
189 $ hg update -C 0 > /dev/null
205 $ hg update -C 0 > /dev/null
190 $ echo changed > a
206 $ echo changed > a
191 $ echo changed > b
207 $ echo changed > b
192 $ chmod +x b
208 $ chmod +x b
193
209
194 Diff in working directory, before:
210 Diff in working directory, before:
195
211
196 $ hg diff --git
212 $ hg diff --git
197 diff --git a/a b/a
213 diff --git a/a b/a
198 --- a/a
214 --- a/a
199 +++ b/a
215 +++ b/a
200 @@ -1,1 +1,1 @@
216 @@ -1,1 +1,1 @@
201 -a
217 -a
202 +changed
218 +changed
203 diff --git a/b b/b
219 diff --git a/b b/b
204 old mode 100644
220 old mode 100644
205 new mode 100755
221 new mode 100755
206 --- a/b
222 --- a/b
207 +++ b/b
223 +++ b/b
208 @@ -1,1 +1,1 @@
224 @@ -1,1 +1,1 @@
209 -b
225 -b
210 +changed
226 +changed
211
227
212
228
213 Edit with extdiff -p:
229 Edit with extdiff -p:
214
230
215 Prepare custom diff/edit tool:
231 Prepare custom diff/edit tool:
216
232
217 $ cat > 'diff tool.py' << EOT
233 $ cat > 'diff tool.py' << EOT
218 > #!/usr/bin/env python
234 > #!/usr/bin/env python
219 > import time
235 > import time
220 > time.sleep(1) # avoid unchanged-timestamp problems
236 > time.sleep(1) # avoid unchanged-timestamp problems
221 > file('a/a', 'ab').write('edited\n')
237 > file('a/a', 'ab').write('edited\n')
222 > file('a/b', 'ab').write('edited\n')
238 > file('a/b', 'ab').write('edited\n')
223 > EOT
239 > EOT
224
240
225 $ chmod +x 'diff tool.py'
241 $ chmod +x 'diff tool.py'
226
242
227 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
243 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
228 and start tool
244 and start tool
229
245
230 $ hg extdiff -p "`pwd`/diff tool.py"
246 $ hg extdiff -p "`pwd`/diff tool.py"
231 [1]
247 [1]
232
248
233 Diff in working directory, after:
249 Diff in working directory, after:
234
250
235 $ hg diff --git
251 $ hg diff --git
236 diff --git a/a b/a
252 diff --git a/a b/a
237 --- a/a
253 --- a/a
238 +++ b/a
254 +++ b/a
239 @@ -1,1 +1,2 @@
255 @@ -1,1 +1,2 @@
240 -a
256 -a
241 +changed
257 +changed
242 +edited
258 +edited
243 diff --git a/b b/b
259 diff --git a/b b/b
244 old mode 100644
260 old mode 100644
245 new mode 100755
261 new mode 100755
246 --- a/b
262 --- a/b
247 +++ b/b
263 +++ b/b
248 @@ -1,1 +1,2 @@
264 @@ -1,1 +1,2 @@
249 -b
265 -b
250 +changed
266 +changed
251 +edited
267 +edited
252
268
253 Test extdiff with --option:
269 Test extdiff with --option:
254
270
255 $ hg extdiff -p echo -o this -c 1
271 $ hg extdiff -p echo -o this -c 1
256 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
272 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
257 [1]
273 [1]
258
274
259 $ hg falabala -o this -c 1
275 $ hg falabala -o this -c 1
260 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
276 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
261 [1]
277 [1]
262
278
263 Test extdiff's handling of options with spaces in them:
279 Test extdiff's handling of options with spaces in them:
264
280
265 $ hg edspace -c 1
281 $ hg edspace -c 1
266 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
282 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
267 [1]
283 [1]
268
284
269 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
285 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
270 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
286 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
271 [1]
287 [1]
272
288
273 Test with revsets:
289 Test with revsets:
274
290
275 $ hg extdif -p echo -c "rev(1)"
291 $ hg extdif -p echo -c "rev(1)"
276 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
292 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
277 [1]
293 [1]
278
294
279 $ hg extdif -p echo -r "0::1"
295 $ hg extdif -p echo -r "0::1"
280 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
296 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
281 [1]
297 [1]
282
298
283 Fallback to merge-tools.tool.executable|regkey
299 Fallback to merge-tools.tool.executable|regkey
284 $ mkdir dir
300 $ mkdir dir
285 $ cat > 'dir/tool.sh' << EOF
301 $ cat > 'dir/tool.sh' << EOF
286 > #!/bin/sh
302 > #!/bin/sh
287 > echo "** custom diff **"
303 > echo "** custom diff **"
288 > EOF
304 > EOF
289 $ chmod +x dir/tool.sh
305 $ chmod +x dir/tool.sh
290 $ tool=`pwd`/dir/tool.sh
306 $ tool=`pwd`/dir/tool.sh
291 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
307 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
292 making snapshot of 2 files from rev * (glob)
308 making snapshot of 2 files from rev * (glob)
293 a
309 a
294 b
310 b
295 making snapshot of 2 files from working directory
311 making snapshot of 2 files from working directory
296 a
312 a
297 b
313 b
298 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
314 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
299 ** custom diff **
315 ** custom diff **
300 cleaning up temp directory
316 cleaning up temp directory
301 [1]
317 [1]
302
318
303 $ cd ..
319 $ cd ..
304
320
305 #endif
321 #endif
306
322
307 #if symlink
323 #if symlink
308
324
309 Test symlinks handling (issue1909)
325 Test symlinks handling (issue1909)
310
326
311 $ hg init testsymlinks
327 $ hg init testsymlinks
312 $ cd testsymlinks
328 $ cd testsymlinks
313 $ echo a > a
329 $ echo a > a
314 $ hg ci -Am adda
330 $ hg ci -Am adda
315 adding a
331 adding a
316 $ echo a >> a
332 $ echo a >> a
317 $ ln -s missing linka
333 $ ln -s missing linka
318 $ hg add linka
334 $ hg add linka
319 $ hg falabala -r 0 --traceback
335 $ hg falabala -r 0 --traceback
320 diffing testsymlinks.07f494440405 testsymlinks
336 diffing testsymlinks.07f494440405 testsymlinks
321 [1]
337 [1]
322 $ cd ..
338 $ cd ..
323
339
324 #endif
340 #endif
General Comments 0
You need to be logged in to leave comments. Login now