##// END OF EJS Templates
posix: quote the specified string only when it may have to be quoted...
FUJIWARA Katsunori -
r23683:5edb3871 default
parent child Browse files
Show More
@@ -1,599 +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
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 def shellquote(s):
319 def shellquote(s):
319 if os.sys.platform == 'OpenVMS':
320 if os.sys.platform == 'OpenVMS':
320 return '"%s"' % s
321 return '"%s"' % s
322 global _needsshellquote
323 if _needsshellquote is None:
324 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/-]').search
325 if not _needsshellquote(s):
326 # "s" shouldn't have to be quoted
327 return s
321 else:
328 else:
322 return "'%s'" % s.replace("'", "'\\''")
329 return "'%s'" % s.replace("'", "'\\''")
323
330
324 def quotecommand(cmd):
331 def quotecommand(cmd):
325 return cmd
332 return cmd
326
333
327 def popen(command, mode='r'):
334 def popen(command, mode='r'):
328 return os.popen(command, mode)
335 return os.popen(command, mode)
329
336
330 def testpid(pid):
337 def testpid(pid):
331 '''return False if pid dead, True if running or not sure'''
338 '''return False if pid dead, True if running or not sure'''
332 if os.sys.platform == 'OpenVMS':
339 if os.sys.platform == 'OpenVMS':
333 return True
340 return True
334 try:
341 try:
335 os.kill(pid, 0)
342 os.kill(pid, 0)
336 return True
343 return True
337 except OSError, inst:
344 except OSError, inst:
338 return inst.errno != errno.ESRCH
345 return inst.errno != errno.ESRCH
339
346
340 def explainexit(code):
347 def explainexit(code):
341 """return a 2-tuple (desc, code) describing a subprocess status
348 """return a 2-tuple (desc, code) describing a subprocess status
342 (codes from kill are negative - not os.system/wait encoding)"""
349 (codes from kill are negative - not os.system/wait encoding)"""
343 if code >= 0:
350 if code >= 0:
344 return _("exited with status %d") % code, code
351 return _("exited with status %d") % code, code
345 return _("killed by signal %d") % -code, -code
352 return _("killed by signal %d") % -code, -code
346
353
347 def isowner(st):
354 def isowner(st):
348 """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."""
349 return st.st_uid == os.getuid()
356 return st.st_uid == os.getuid()
350
357
351 def findexe(command):
358 def findexe(command):
352 '''Find executable for command searching like which does.
359 '''Find executable for command searching like which does.
353 If command is a basename then PATH is searched for command.
360 If command is a basename then PATH is searched for command.
354 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.
355 If command isn't found None is returned.'''
362 If command isn't found None is returned.'''
356 if sys.platform == 'OpenVMS':
363 if sys.platform == 'OpenVMS':
357 return command
364 return command
358
365
359 def findexisting(executable):
366 def findexisting(executable):
360 'Will return executable if existing file'
367 'Will return executable if existing file'
361 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):
362 return executable
369 return executable
363 return None
370 return None
364
371
365 if os.sep in command:
372 if os.sep in command:
366 return findexisting(command)
373 return findexisting(command)
367
374
368 if sys.platform == 'plan9':
375 if sys.platform == 'plan9':
369 return findexisting(os.path.join('/bin', command))
376 return findexisting(os.path.join('/bin', command))
370
377
371 for path in os.environ.get('PATH', '').split(os.pathsep):
378 for path in os.environ.get('PATH', '').split(os.pathsep):
372 executable = findexisting(os.path.join(path, command))
379 executable = findexisting(os.path.join(path, command))
373 if executable is not None:
380 if executable is not None:
374 return executable
381 return executable
375 return None
382 return None
376
383
377 def setsignalhandler():
384 def setsignalhandler():
378 pass
385 pass
379
386
380 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
387 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
381
388
382 def statfiles(files):
389 def statfiles(files):
383 '''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
384 exist or has a type we don't care about.'''
391 exist or has a type we don't care about.'''
385 lstat = os.lstat
392 lstat = os.lstat
386 getkind = stat.S_IFMT
393 getkind = stat.S_IFMT
387 for nf in files:
394 for nf in files:
388 try:
395 try:
389 st = lstat(nf)
396 st = lstat(nf)
390 if getkind(st.st_mode) not in _wantedkinds:
397 if getkind(st.st_mode) not in _wantedkinds:
391 st = None
398 st = None
392 except OSError, err:
399 except OSError, err:
393 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
400 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
394 raise
401 raise
395 st = None
402 st = None
396 yield st
403 yield st
397
404
398 def getuser():
405 def getuser():
399 '''return name of current user'''
406 '''return name of current user'''
400 return getpass.getuser()
407 return getpass.getuser()
401
408
402 def username(uid=None):
409 def username(uid=None):
403 """Return the name of the user with the given uid.
410 """Return the name of the user with the given uid.
404
411
405 If uid is None, return the name of the current user."""
412 If uid is None, return the name of the current user."""
406
413
407 if uid is None:
414 if uid is None:
408 uid = os.getuid()
415 uid = os.getuid()
409 try:
416 try:
410 return pwd.getpwuid(uid)[0]
417 return pwd.getpwuid(uid)[0]
411 except KeyError:
418 except KeyError:
412 return str(uid)
419 return str(uid)
413
420
414 def groupname(gid=None):
421 def groupname(gid=None):
415 """Return the name of the group with the given gid.
422 """Return the name of the group with the given gid.
416
423
417 If gid is None, return the name of the current group."""
424 If gid is None, return the name of the current group."""
418
425
419 if gid is None:
426 if gid is None:
420 gid = os.getgid()
427 gid = os.getgid()
421 try:
428 try:
422 return grp.getgrgid(gid)[0]
429 return grp.getgrgid(gid)[0]
423 except KeyError:
430 except KeyError:
424 return str(gid)
431 return str(gid)
425
432
426 def groupmembers(name):
433 def groupmembers(name):
427 """Return the list of members of the group with the given
434 """Return the list of members of the group with the given
428 name, KeyError if the group does not exist.
435 name, KeyError if the group does not exist.
429 """
436 """
430 return list(grp.getgrnam(name).gr_mem)
437 return list(grp.getgrnam(name).gr_mem)
431
438
432 def spawndetached(args):
439 def spawndetached(args):
433 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
440 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
434 args[0], args)
441 args[0], args)
435
442
436 def gethgcmd():
443 def gethgcmd():
437 return sys.argv[:1]
444 return sys.argv[:1]
438
445
439 def termwidth():
446 def termwidth():
440 try:
447 try:
441 import termios, array
448 import termios, array
442 for dev in (sys.stderr, sys.stdout, sys.stdin):
449 for dev in (sys.stderr, sys.stdout, sys.stdin):
443 try:
450 try:
444 try:
451 try:
445 fd = dev.fileno()
452 fd = dev.fileno()
446 except AttributeError:
453 except AttributeError:
447 continue
454 continue
448 if not os.isatty(fd):
455 if not os.isatty(fd):
449 continue
456 continue
450 try:
457 try:
451 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
458 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
452 width = array.array('h', arri)[1]
459 width = array.array('h', arri)[1]
453 if width > 0:
460 if width > 0:
454 return width
461 return width
455 except AttributeError:
462 except AttributeError:
456 pass
463 pass
457 except ValueError:
464 except ValueError:
458 pass
465 pass
459 except IOError, e:
466 except IOError, e:
460 if e[0] == errno.EINVAL:
467 if e[0] == errno.EINVAL:
461 pass
468 pass
462 else:
469 else:
463 raise
470 raise
464 except ImportError:
471 except ImportError:
465 pass
472 pass
466 return 80
473 return 80
467
474
468 def makedir(path, notindexed):
475 def makedir(path, notindexed):
469 os.mkdir(path)
476 os.mkdir(path)
470
477
471 def unlinkpath(f, ignoremissing=False):
478 def unlinkpath(f, ignoremissing=False):
472 """unlink and remove the directory if it is empty"""
479 """unlink and remove the directory if it is empty"""
473 try:
480 try:
474 os.unlink(f)
481 os.unlink(f)
475 except OSError, e:
482 except OSError, e:
476 if not (ignoremissing and e.errno == errno.ENOENT):
483 if not (ignoremissing and e.errno == errno.ENOENT):
477 raise
484 raise
478 # try removing directories that might now be empty
485 # try removing directories that might now be empty
479 try:
486 try:
480 os.removedirs(os.path.dirname(f))
487 os.removedirs(os.path.dirname(f))
481 except OSError:
488 except OSError:
482 pass
489 pass
483
490
484 def lookupreg(key, name=None, scope=None):
491 def lookupreg(key, name=None, scope=None):
485 return None
492 return None
486
493
487 def hidewindow():
494 def hidewindow():
488 """Hide current shell window.
495 """Hide current shell window.
489
496
490 Used to hide the window opened when starting asynchronous
497 Used to hide the window opened when starting asynchronous
491 child process under Windows, unneeded on other systems.
498 child process under Windows, unneeded on other systems.
492 """
499 """
493 pass
500 pass
494
501
495 class cachestat(object):
502 class cachestat(object):
496 def __init__(self, path):
503 def __init__(self, path):
497 self.stat = os.stat(path)
504 self.stat = os.stat(path)
498
505
499 def cacheable(self):
506 def cacheable(self):
500 return bool(self.stat.st_ino)
507 return bool(self.stat.st_ino)
501
508
502 __hash__ = object.__hash__
509 __hash__ = object.__hash__
503
510
504 def __eq__(self, other):
511 def __eq__(self, other):
505 try:
512 try:
506 # 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
507 # of these, we shouldn't compare atime but should compare the
514 # of these, we shouldn't compare atime but should compare the
508 # rest. However, one of the other fields changing indicates
515 # rest. However, one of the other fields changing indicates
509 # something fishy going on, so return False if anything but atime
516 # something fishy going on, so return False if anything but atime
510 # changes.
517 # changes.
511 return (self.stat.st_mode == other.stat.st_mode and
518 return (self.stat.st_mode == other.stat.st_mode and
512 self.stat.st_ino == other.stat.st_ino and
519 self.stat.st_ino == other.stat.st_ino and
513 self.stat.st_dev == other.stat.st_dev and
520 self.stat.st_dev == other.stat.st_dev and
514 self.stat.st_nlink == other.stat.st_nlink and
521 self.stat.st_nlink == other.stat.st_nlink and
515 self.stat.st_uid == other.stat.st_uid and
522 self.stat.st_uid == other.stat.st_uid and
516 self.stat.st_gid == other.stat.st_gid and
523 self.stat.st_gid == other.stat.st_gid and
517 self.stat.st_size == other.stat.st_size and
524 self.stat.st_size == other.stat.st_size and
518 self.stat.st_mtime == other.stat.st_mtime and
525 self.stat.st_mtime == other.stat.st_mtime and
519 self.stat.st_ctime == other.stat.st_ctime)
526 self.stat.st_ctime == other.stat.st_ctime)
520 except AttributeError:
527 except AttributeError:
521 return False
528 return False
522
529
523 def __ne__(self, other):
530 def __ne__(self, other):
524 return not self == other
531 return not self == other
525
532
526 def executablepath():
533 def executablepath():
527 return None # available on Windows only
534 return None # available on Windows only
528
535
529 class unixdomainserver(socket.socket):
536 class unixdomainserver(socket.socket):
530 def __init__(self, join, subsystem):
537 def __init__(self, join, subsystem):
531 '''Create a unix domain socket with the given prefix.'''
538 '''Create a unix domain socket with the given prefix.'''
532 super(unixdomainserver, self).__init__(socket.AF_UNIX)
539 super(unixdomainserver, self).__init__(socket.AF_UNIX)
533 sockname = subsystem + '.sock'
540 sockname = subsystem + '.sock'
534 self.realpath = self.path = join(sockname)
541 self.realpath = self.path = join(sockname)
535 if os.path.islink(self.path):
542 if os.path.islink(self.path):
536 if os.path.exists(self.path):
543 if os.path.exists(self.path):
537 self.realpath = os.readlink(self.path)
544 self.realpath = os.readlink(self.path)
538 else:
545 else:
539 os.unlink(self.path)
546 os.unlink(self.path)
540 try:
547 try:
541 self.bind(self.realpath)
548 self.bind(self.realpath)
542 except socket.error, err:
549 except socket.error, err:
543 if err.args[0] == 'AF_UNIX path too long':
550 if err.args[0] == 'AF_UNIX path too long':
544 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
551 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
545 self.realpath = os.path.join(tmpdir, sockname)
552 self.realpath = os.path.join(tmpdir, sockname)
546 try:
553 try:
547 self.bind(self.realpath)
554 self.bind(self.realpath)
548 os.symlink(self.realpath, self.path)
555 os.symlink(self.realpath, self.path)
549 except (OSError, socket.error):
556 except (OSError, socket.error):
550 self.cleanup()
557 self.cleanup()
551 raise
558 raise
552 else:
559 else:
553 raise
560 raise
554 self.listen(5)
561 self.listen(5)
555
562
556 def cleanup(self):
563 def cleanup(self):
557 def okayifmissing(f, path):
564 def okayifmissing(f, path):
558 try:
565 try:
559 f(path)
566 f(path)
560 except OSError, err:
567 except OSError, err:
561 if err.errno != errno.ENOENT:
568 if err.errno != errno.ENOENT:
562 raise
569 raise
563
570
564 okayifmissing(os.unlink, self.path)
571 okayifmissing(os.unlink, self.path)
565 if self.realpath != self.path:
572 if self.realpath != self.path:
566 okayifmissing(os.unlink, self.realpath)
573 okayifmissing(os.unlink, self.realpath)
567 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
574 okayifmissing(os.rmdir, os.path.dirname(self.realpath))
568
575
569 def statislink(st):
576 def statislink(st):
570 '''check whether a stat result is a symlink'''
577 '''check whether a stat result is a symlink'''
571 return st and stat.S_ISLNK(st.st_mode)
578 return st and stat.S_ISLNK(st.st_mode)
572
579
573 def statisexec(st):
580 def statisexec(st):
574 '''check whether a stat result is an executable file'''
581 '''check whether a stat result is an executable file'''
575 return st and (st.st_mode & 0100 != 0)
582 return st and (st.st_mode & 0100 != 0)
576
583
577 def readpipe(pipe):
584 def readpipe(pipe):
578 """Read all available data from a pipe."""
585 """Read all available data from a pipe."""
579 # 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.
580 # 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
581 # that's available.
588 # that's available.
582 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
589 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
583 flags |= os.O_NONBLOCK
590 flags |= os.O_NONBLOCK
584 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
591 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
585
592
586 try:
593 try:
587 chunks = []
594 chunks = []
588 while True:
595 while True:
589 try:
596 try:
590 s = pipe.read()
597 s = pipe.read()
591 if not s:
598 if not s:
592 break
599 break
593 chunks.append(s)
600 chunks.append(s)
594 except IOError:
601 except IOError:
595 break
602 break
596
603
597 return ''.join(chunks)
604 return ''.join(chunks)
598 finally:
605 finally:
599 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
606 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
@@ -1,302 +1,302 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 'being quoted' | grep '^running'
157 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
158 running "echo '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 'being quoted' | grep '^running'
159 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
160 running "'echo' '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 #if execbit
163 #if execbit
164
164
165 Test extdiff of multiple files in tmp dir:
165 Test extdiff of multiple files in tmp dir:
166
166
167 $ hg update -C 0 > /dev/null
167 $ hg update -C 0 > /dev/null
168 $ echo changed > a
168 $ echo changed > a
169 $ echo changed > b
169 $ echo changed > b
170 $ chmod +x b
170 $ chmod +x b
171
171
172 Diff in working directory, before:
172 Diff in working directory, before:
173
173
174 $ hg diff --git
174 $ hg diff --git
175 diff --git a/a b/a
175 diff --git a/a b/a
176 --- a/a
176 --- a/a
177 +++ b/a
177 +++ b/a
178 @@ -1,1 +1,1 @@
178 @@ -1,1 +1,1 @@
179 -a
179 -a
180 +changed
180 +changed
181 diff --git a/b b/b
181 diff --git a/b b/b
182 old mode 100644
182 old mode 100644
183 new mode 100755
183 new mode 100755
184 --- a/b
184 --- a/b
185 +++ b/b
185 +++ b/b
186 @@ -1,1 +1,1 @@
186 @@ -1,1 +1,1 @@
187 -b
187 -b
188 +changed
188 +changed
189
189
190
190
191 Edit with extdiff -p:
191 Edit with extdiff -p:
192
192
193 Prepare custom diff/edit tool:
193 Prepare custom diff/edit tool:
194
194
195 $ cat > 'diff tool.py' << EOT
195 $ cat > 'diff tool.py' << EOT
196 > #!/usr/bin/env python
196 > #!/usr/bin/env python
197 > import time
197 > import time
198 > time.sleep(1) # avoid unchanged-timestamp problems
198 > time.sleep(1) # avoid unchanged-timestamp problems
199 > file('a/a', 'ab').write('edited\n')
199 > file('a/a', 'ab').write('edited\n')
200 > file('a/b', 'ab').write('edited\n')
200 > file('a/b', 'ab').write('edited\n')
201 > EOT
201 > EOT
202
202
203 $ chmod +x 'diff tool.py'
203 $ chmod +x 'diff tool.py'
204
204
205 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
205 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
206 and start tool
206 and start tool
207
207
208 $ hg extdiff -p "`pwd`/diff tool.py"
208 $ hg extdiff -p "`pwd`/diff tool.py"
209 [1]
209 [1]
210
210
211 Diff in working directory, after:
211 Diff in working directory, after:
212
212
213 $ hg diff --git
213 $ hg diff --git
214 diff --git a/a b/a
214 diff --git a/a b/a
215 --- a/a
215 --- a/a
216 +++ b/a
216 +++ b/a
217 @@ -1,1 +1,2 @@
217 @@ -1,1 +1,2 @@
218 -a
218 -a
219 +changed
219 +changed
220 +edited
220 +edited
221 diff --git a/b b/b
221 diff --git a/b b/b
222 old mode 100644
222 old mode 100644
223 new mode 100755
223 new mode 100755
224 --- a/b
224 --- a/b
225 +++ b/b
225 +++ b/b
226 @@ -1,1 +1,2 @@
226 @@ -1,1 +1,2 @@
227 -b
227 -b
228 +changed
228 +changed
229 +edited
229 +edited
230
230
231 Test extdiff with --option:
231 Test extdiff with --option:
232
232
233 $ hg extdiff -p echo -o this -c 1
233 $ hg extdiff -p echo -o this -c 1
234 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
234 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
235 [1]
235 [1]
236
236
237 $ hg falabala -o this -c 1
237 $ hg falabala -o this -c 1
238 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
238 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
239 [1]
239 [1]
240
240
241 Test extdiff's handling of options with spaces in them:
241 Test extdiff's handling of options with spaces in them:
242
242
243 $ hg edspace -c 1
243 $ hg edspace -c 1
244 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
244 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
245 [1]
245 [1]
246
246
247 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
247 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
248 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
248 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
249 [1]
249 [1]
250
250
251 Test with revsets:
251 Test with revsets:
252
252
253 $ hg extdif -p echo -c "rev(1)"
253 $ hg extdif -p echo -c "rev(1)"
254 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
254 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
255 [1]
255 [1]
256
256
257 $ hg extdif -p echo -r "0::1"
257 $ hg extdif -p echo -r "0::1"
258 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
258 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob)
259 [1]
259 [1]
260
260
261 Fallback to merge-tools.tool.executable|regkey
261 Fallback to merge-tools.tool.executable|regkey
262 $ mkdir dir
262 $ mkdir dir
263 $ cat > 'dir/tool.sh' << EOF
263 $ cat > 'dir/tool.sh' << EOF
264 > #!/bin/sh
264 > #!/bin/sh
265 > echo "** custom diff **"
265 > echo "** custom diff **"
266 > EOF
266 > EOF
267 $ chmod +x dir/tool.sh
267 $ chmod +x dir/tool.sh
268 $ tool=`pwd`/dir/tool.sh
268 $ tool=`pwd`/dir/tool.sh
269 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
269 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
270 making snapshot of 2 files from rev * (glob)
270 making snapshot of 2 files from rev * (glob)
271 a
271 a
272 b
272 b
273 making snapshot of 2 files from working directory
273 making snapshot of 2 files from working directory
274 a
274 a
275 b
275 b
276 running "'$TESTTMP/a/dir/tool.sh' 'a.*' 'a'" in */extdiff.* (glob)
276 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
277 ** custom diff **
277 ** custom diff **
278 cleaning up temp directory
278 cleaning up temp directory
279 [1]
279 [1]
280
280
281 $ cd ..
281 $ cd ..
282
282
283 #endif
283 #endif
284
284
285 #if symlink
285 #if symlink
286
286
287 Test symlinks handling (issue1909)
287 Test symlinks handling (issue1909)
288
288
289 $ hg init testsymlinks
289 $ hg init testsymlinks
290 $ cd testsymlinks
290 $ cd testsymlinks
291 $ echo a > a
291 $ echo a > a
292 $ hg ci -Am adda
292 $ hg ci -Am adda
293 adding a
293 adding a
294 $ echo a >> a
294 $ echo a >> a
295 $ ln -s missing linka
295 $ ln -s missing linka
296 $ hg add linka
296 $ hg add linka
297 $ hg falabala -r 0 --traceback
297 $ hg falabala -r 0 --traceback
298 diffing testsymlinks.07f494440405 testsymlinks
298 diffing testsymlinks.07f494440405 testsymlinks
299 [1]
299 [1]
300 $ cd ..
300 $ cd ..
301
301
302 #endif
302 #endif
General Comments 0
You need to be logged in to leave comments. Login now