##// END OF EJS Templates
if a filename contains spaces, patch adds quote around it
Benoit Boissinot -
r1593:6bb3463b default
parent child Browse files
Show More
@@ -1,661 +1,664 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8
8
9 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 import os, errno
13 import os, errno
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import *
15 from demandload import *
16 demandload(globals(), "re cStringIO shutil popen2 sys tempfile threading time")
16 demandload(globals(), "re cStringIO shutil popen2 sys tempfile threading time")
17
17
18 def pipefilter(s, cmd):
18 def pipefilter(s, cmd):
19 '''filter string S through command CMD, returning its output'''
19 '''filter string S through command CMD, returning its output'''
20 (pout, pin) = popen2.popen2(cmd, -1, 'b')
20 (pout, pin) = popen2.popen2(cmd, -1, 'b')
21 def writer():
21 def writer():
22 pin.write(s)
22 pin.write(s)
23 pin.close()
23 pin.close()
24
24
25 # we should use select instead on UNIX, but this will work on most
25 # we should use select instead on UNIX, but this will work on most
26 # systems, including Windows
26 # systems, including Windows
27 w = threading.Thread(target=writer)
27 w = threading.Thread(target=writer)
28 w.start()
28 w.start()
29 f = pout.read()
29 f = pout.read()
30 pout.close()
30 pout.close()
31 w.join()
31 w.join()
32 return f
32 return f
33
33
34 def tempfilter(s, cmd):
34 def tempfilter(s, cmd):
35 '''filter string S through a pair of temporary files with CMD.
35 '''filter string S through a pair of temporary files with CMD.
36 CMD is used as a template to create the real command to be run,
36 CMD is used as a template to create the real command to be run,
37 with the strings INFILE and OUTFILE replaced by the real names of
37 with the strings INFILE and OUTFILE replaced by the real names of
38 the temporary files generated.'''
38 the temporary files generated.'''
39 inname, outname = None, None
39 inname, outname = None, None
40 try:
40 try:
41 infd, inname = tempfile.mkstemp(prefix='hgfin')
41 infd, inname = tempfile.mkstemp(prefix='hgfin')
42 fp = os.fdopen(infd, 'wb')
42 fp = os.fdopen(infd, 'wb')
43 fp.write(s)
43 fp.write(s)
44 fp.close()
44 fp.close()
45 outfd, outname = tempfile.mkstemp(prefix='hgfout')
45 outfd, outname = tempfile.mkstemp(prefix='hgfout')
46 os.close(outfd)
46 os.close(outfd)
47 cmd = cmd.replace('INFILE', inname)
47 cmd = cmd.replace('INFILE', inname)
48 cmd = cmd.replace('OUTFILE', outname)
48 cmd = cmd.replace('OUTFILE', outname)
49 code = os.system(cmd)
49 code = os.system(cmd)
50 if code: raise Abort(_("command '%s' failed: %s") %
50 if code: raise Abort(_("command '%s' failed: %s") %
51 (cmd, explain_exit(code)))
51 (cmd, explain_exit(code)))
52 return open(outname, 'rb').read()
52 return open(outname, 'rb').read()
53 finally:
53 finally:
54 try:
54 try:
55 if inname: os.unlink(inname)
55 if inname: os.unlink(inname)
56 except: pass
56 except: pass
57 try:
57 try:
58 if outname: os.unlink(outname)
58 if outname: os.unlink(outname)
59 except: pass
59 except: pass
60
60
61 filtertable = {
61 filtertable = {
62 'tempfile:': tempfilter,
62 'tempfile:': tempfilter,
63 'pipe:': pipefilter,
63 'pipe:': pipefilter,
64 }
64 }
65
65
66 def filter(s, cmd):
66 def filter(s, cmd):
67 "filter a string through a command that transforms its input to its output"
67 "filter a string through a command that transforms its input to its output"
68 for name, fn in filtertable.iteritems():
68 for name, fn in filtertable.iteritems():
69 if cmd.startswith(name):
69 if cmd.startswith(name):
70 return fn(s, cmd[len(name):].lstrip())
70 return fn(s, cmd[len(name):].lstrip())
71 return pipefilter(s, cmd)
71 return pipefilter(s, cmd)
72
72
73 def patch(strip, patchname, ui):
73 def patch(strip, patchname, ui):
74 """apply the patch <patchname> to the working directory.
74 """apply the patch <patchname> to the working directory.
75 a list of patched files is returned"""
75 a list of patched files is returned"""
76 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
76 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
77 files = {}
77 files = {}
78 for line in fp:
78 for line in fp:
79 line = line.rstrip()
79 line = line.rstrip()
80 ui.status("%s\n" % line)
80 ui.status("%s\n" % line)
81 if line.startswith('patching file '):
81 if line.startswith('patching file '):
82 pf = parse_patch_output(line)
82 pf = parse_patch_output(line)
83 files.setdefault(pf, 1)
83 files.setdefault(pf, 1)
84 code = fp.close()
84 code = fp.close()
85 if code:
85 if code:
86 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
86 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
87 return files.keys()
87 return files.keys()
88
88
89 def binary(s):
89 def binary(s):
90 """return true if a string is binary data using diff's heuristic"""
90 """return true if a string is binary data using diff's heuristic"""
91 if s and '\0' in s[:4096]:
91 if s and '\0' in s[:4096]:
92 return True
92 return True
93 return False
93 return False
94
94
95 def unique(g):
95 def unique(g):
96 """return the uniq elements of iterable g"""
96 """return the uniq elements of iterable g"""
97 seen = {}
97 seen = {}
98 for f in g:
98 for f in g:
99 if f not in seen:
99 if f not in seen:
100 seen[f] = 1
100 seen[f] = 1
101 yield f
101 yield f
102
102
103 class Abort(Exception):
103 class Abort(Exception):
104 """Raised if a command needs to print an error and exit."""
104 """Raised if a command needs to print an error and exit."""
105
105
106 def always(fn): return True
106 def always(fn): return True
107 def never(fn): return False
107 def never(fn): return False
108
108
109 def patkind(name, dflt_pat='glob'):
109 def patkind(name, dflt_pat='glob'):
110 """Split a string into an optional pattern kind prefix and the
110 """Split a string into an optional pattern kind prefix and the
111 actual pattern."""
111 actual pattern."""
112 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
112 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
113 if name.startswith(prefix + ':'): return name.split(':', 1)
113 if name.startswith(prefix + ':'): return name.split(':', 1)
114 return dflt_pat, name
114 return dflt_pat, name
115
115
116 def globre(pat, head='^', tail='$'):
116 def globre(pat, head='^', tail='$'):
117 "convert a glob pattern into a regexp"
117 "convert a glob pattern into a regexp"
118 i, n = 0, len(pat)
118 i, n = 0, len(pat)
119 res = ''
119 res = ''
120 group = False
120 group = False
121 def peek(): return i < n and pat[i]
121 def peek(): return i < n and pat[i]
122 while i < n:
122 while i < n:
123 c = pat[i]
123 c = pat[i]
124 i = i+1
124 i = i+1
125 if c == '*':
125 if c == '*':
126 if peek() == '*':
126 if peek() == '*':
127 i += 1
127 i += 1
128 res += '.*'
128 res += '.*'
129 else:
129 else:
130 res += '[^/]*'
130 res += '[^/]*'
131 elif c == '?':
131 elif c == '?':
132 res += '.'
132 res += '.'
133 elif c == '[':
133 elif c == '[':
134 j = i
134 j = i
135 if j < n and pat[j] in '!]':
135 if j < n and pat[j] in '!]':
136 j += 1
136 j += 1
137 while j < n and pat[j] != ']':
137 while j < n and pat[j] != ']':
138 j += 1
138 j += 1
139 if j >= n:
139 if j >= n:
140 res += '\\['
140 res += '\\['
141 else:
141 else:
142 stuff = pat[i:j].replace('\\','\\\\')
142 stuff = pat[i:j].replace('\\','\\\\')
143 i = j + 1
143 i = j + 1
144 if stuff[0] == '!':
144 if stuff[0] == '!':
145 stuff = '^' + stuff[1:]
145 stuff = '^' + stuff[1:]
146 elif stuff[0] == '^':
146 elif stuff[0] == '^':
147 stuff = '\\' + stuff
147 stuff = '\\' + stuff
148 res = '%s[%s]' % (res, stuff)
148 res = '%s[%s]' % (res, stuff)
149 elif c == '{':
149 elif c == '{':
150 group = True
150 group = True
151 res += '(?:'
151 res += '(?:'
152 elif c == '}' and group:
152 elif c == '}' and group:
153 res += ')'
153 res += ')'
154 group = False
154 group = False
155 elif c == ',' and group:
155 elif c == ',' and group:
156 res += '|'
156 res += '|'
157 else:
157 else:
158 res += re.escape(c)
158 res += re.escape(c)
159 return head + res + tail
159 return head + res + tail
160
160
161 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
161 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
162
162
163 def pathto(n1, n2):
163 def pathto(n1, n2):
164 '''return the relative path from one place to another.
164 '''return the relative path from one place to another.
165 this returns a path in the form used by the local filesystem, not hg.'''
165 this returns a path in the form used by the local filesystem, not hg.'''
166 if not n1: return localpath(n2)
166 if not n1: return localpath(n2)
167 a, b = n1.split('/'), n2.split('/')
167 a, b = n1.split('/'), n2.split('/')
168 a.reverse()
168 a.reverse()
169 b.reverse()
169 b.reverse()
170 while a and b and a[-1] == b[-1]:
170 while a and b and a[-1] == b[-1]:
171 a.pop()
171 a.pop()
172 b.pop()
172 b.pop()
173 b.reverse()
173 b.reverse()
174 return os.sep.join((['..'] * len(a)) + b)
174 return os.sep.join((['..'] * len(a)) + b)
175
175
176 def canonpath(root, cwd, myname):
176 def canonpath(root, cwd, myname):
177 """return the canonical path of myname, given cwd and root"""
177 """return the canonical path of myname, given cwd and root"""
178 if root == os.sep:
178 if root == os.sep:
179 rootsep = os.sep
179 rootsep = os.sep
180 else:
180 else:
181 rootsep = root + os.sep
181 rootsep = root + os.sep
182 name = myname
182 name = myname
183 if not name.startswith(os.sep):
183 if not name.startswith(os.sep):
184 name = os.path.join(root, cwd, name)
184 name = os.path.join(root, cwd, name)
185 name = os.path.normpath(name)
185 name = os.path.normpath(name)
186 if name.startswith(rootsep):
186 if name.startswith(rootsep):
187 return pconvert(name[len(rootsep):])
187 return pconvert(name[len(rootsep):])
188 elif name == root:
188 elif name == root:
189 return ''
189 return ''
190 else:
190 else:
191 raise Abort('%s not under root' % myname)
191 raise Abort('%s not under root' % myname)
192
192
193 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
193 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
194 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob')
194 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob')
195
195
196 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
196 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
197 if os.name == 'nt':
197 if os.name == 'nt':
198 dflt_pat = 'glob'
198 dflt_pat = 'glob'
199 else:
199 else:
200 dflt_pat = 'relpath'
200 dflt_pat = 'relpath'
201 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat)
201 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat)
202
202
203 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat):
203 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat):
204 """build a function to match a set of file patterns
204 """build a function to match a set of file patterns
205
205
206 arguments:
206 arguments:
207 canonroot - the canonical root of the tree you're matching against
207 canonroot - the canonical root of the tree you're matching against
208 cwd - the current working directory, if relevant
208 cwd - the current working directory, if relevant
209 names - patterns to find
209 names - patterns to find
210 inc - patterns to include
210 inc - patterns to include
211 exc - patterns to exclude
211 exc - patterns to exclude
212 head - a regex to prepend to patterns to control whether a match is rooted
212 head - a regex to prepend to patterns to control whether a match is rooted
213
213
214 a pattern is one of:
214 a pattern is one of:
215 'glob:<rooted glob>'
215 'glob:<rooted glob>'
216 're:<rooted regexp>'
216 're:<rooted regexp>'
217 'path:<rooted path>'
217 'path:<rooted path>'
218 'relglob:<relative glob>'
218 'relglob:<relative glob>'
219 'relpath:<relative path>'
219 'relpath:<relative path>'
220 'relre:<relative regexp>'
220 'relre:<relative regexp>'
221 '<rooted path or regexp>'
221 '<rooted path or regexp>'
222
222
223 returns:
223 returns:
224 a 3-tuple containing
224 a 3-tuple containing
225 - list of explicit non-pattern names passed in
225 - list of explicit non-pattern names passed in
226 - a bool match(filename) function
226 - a bool match(filename) function
227 - a bool indicating if any patterns were passed in
227 - a bool indicating if any patterns were passed in
228
228
229 todo:
229 todo:
230 make head regex a rooted bool
230 make head regex a rooted bool
231 """
231 """
232
232
233 def contains_glob(name):
233 def contains_glob(name):
234 for c in name:
234 for c in name:
235 if c in _globchars: return True
235 if c in _globchars: return True
236 return False
236 return False
237
237
238 def regex(kind, name, tail):
238 def regex(kind, name, tail):
239 '''convert a pattern into a regular expression'''
239 '''convert a pattern into a regular expression'''
240 if kind == 're':
240 if kind == 're':
241 return name
241 return name
242 elif kind == 'path':
242 elif kind == 'path':
243 return '^' + re.escape(name) + '(?:/|$)'
243 return '^' + re.escape(name) + '(?:/|$)'
244 elif kind == 'relglob':
244 elif kind == 'relglob':
245 return head + globre(name, '(?:|.*/)', tail)
245 return head + globre(name, '(?:|.*/)', tail)
246 elif kind == 'relpath':
246 elif kind == 'relpath':
247 return head + re.escape(name) + tail
247 return head + re.escape(name) + tail
248 elif kind == 'relre':
248 elif kind == 'relre':
249 if name.startswith('^'):
249 if name.startswith('^'):
250 return name
250 return name
251 return '.*' + name
251 return '.*' + name
252 return head + globre(name, '', tail)
252 return head + globre(name, '', tail)
253
253
254 def matchfn(pats, tail):
254 def matchfn(pats, tail):
255 """build a matching function from a set of patterns"""
255 """build a matching function from a set of patterns"""
256 if not pats:
256 if not pats:
257 return
257 return
258 matches = []
258 matches = []
259 for k, p in pats:
259 for k, p in pats:
260 try:
260 try:
261 pat = '(?:%s)' % regex(k, p, tail)
261 pat = '(?:%s)' % regex(k, p, tail)
262 matches.append(re.compile(pat).match)
262 matches.append(re.compile(pat).match)
263 except re.error:
263 except re.error:
264 raise Abort("invalid pattern: %s:%s" % (k, p))
264 raise Abort("invalid pattern: %s:%s" % (k, p))
265
265
266 def buildfn(text):
266 def buildfn(text):
267 for m in matches:
267 for m in matches:
268 r = m(text)
268 r = m(text)
269 if r:
269 if r:
270 return r
270 return r
271
271
272 return buildfn
272 return buildfn
273
273
274 def globprefix(pat):
274 def globprefix(pat):
275 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
275 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
276 root = []
276 root = []
277 for p in pat.split(os.sep):
277 for p in pat.split(os.sep):
278 if contains_glob(p): break
278 if contains_glob(p): break
279 root.append(p)
279 root.append(p)
280 return '/'.join(root)
280 return '/'.join(root)
281
281
282 pats = []
282 pats = []
283 files = []
283 files = []
284 roots = []
284 roots = []
285 for kind, name in [patkind(p, dflt_pat) for p in names]:
285 for kind, name in [patkind(p, dflt_pat) for p in names]:
286 if kind in ('glob', 'relpath'):
286 if kind in ('glob', 'relpath'):
287 name = canonpath(canonroot, cwd, name)
287 name = canonpath(canonroot, cwd, name)
288 if name == '':
288 if name == '':
289 kind, name = 'glob', '**'
289 kind, name = 'glob', '**'
290 if kind in ('glob', 'path', 're'):
290 if kind in ('glob', 'path', 're'):
291 pats.append((kind, name))
291 pats.append((kind, name))
292 if kind == 'glob':
292 if kind == 'glob':
293 root = globprefix(name)
293 root = globprefix(name)
294 if root: roots.append(root)
294 if root: roots.append(root)
295 elif kind == 'relpath':
295 elif kind == 'relpath':
296 files.append((kind, name))
296 files.append((kind, name))
297 roots.append(name)
297 roots.append(name)
298
298
299 patmatch = matchfn(pats, '$') or always
299 patmatch = matchfn(pats, '$') or always
300 filematch = matchfn(files, '(?:/|$)') or always
300 filematch = matchfn(files, '(?:/|$)') or always
301 incmatch = always
301 incmatch = always
302 if inc:
302 if inc:
303 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
303 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
304 excmatch = lambda fn: False
304 excmatch = lambda fn: False
305 if exc:
305 if exc:
306 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
306 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
307
307
308 return (roots,
308 return (roots,
309 lambda fn: (incmatch(fn) and not excmatch(fn) and
309 lambda fn: (incmatch(fn) and not excmatch(fn) and
310 (fn.endswith('/') or
310 (fn.endswith('/') or
311 (not pats and not files) or
311 (not pats and not files) or
312 (pats and patmatch(fn)) or
312 (pats and patmatch(fn)) or
313 (files and filematch(fn)))),
313 (files and filematch(fn)))),
314 (inc or exc or (pats and pats != [('glob', '**')])) and True)
314 (inc or exc or (pats and pats != [('glob', '**')])) and True)
315
315
316 def system(cmd, errprefix=None):
316 def system(cmd, errprefix=None):
317 """execute a shell command that must succeed"""
317 """execute a shell command that must succeed"""
318 rc = os.system(cmd)
318 rc = os.system(cmd)
319 if rc:
319 if rc:
320 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
320 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
321 explain_exit(rc)[0])
321 explain_exit(rc)[0])
322 if errprefix:
322 if errprefix:
323 errmsg = "%s: %s" % (errprefix, errmsg)
323 errmsg = "%s: %s" % (errprefix, errmsg)
324 raise Abort(errmsg)
324 raise Abort(errmsg)
325
325
326 def rename(src, dst):
326 def rename(src, dst):
327 """forcibly rename a file"""
327 """forcibly rename a file"""
328 try:
328 try:
329 os.rename(src, dst)
329 os.rename(src, dst)
330 except:
330 except:
331 os.unlink(dst)
331 os.unlink(dst)
332 os.rename(src, dst)
332 os.rename(src, dst)
333
333
334 def unlink(f):
334 def unlink(f):
335 """unlink and remove the directory if it is empty"""
335 """unlink and remove the directory if it is empty"""
336 os.unlink(f)
336 os.unlink(f)
337 # try removing directories that might now be empty
337 # try removing directories that might now be empty
338 try: os.removedirs(os.path.dirname(f))
338 try: os.removedirs(os.path.dirname(f))
339 except: pass
339 except: pass
340
340
341 def copyfiles(src, dst, hardlink=None):
341 def copyfiles(src, dst, hardlink=None):
342 """Copy a directory tree using hardlinks if possible"""
342 """Copy a directory tree using hardlinks if possible"""
343
343
344 if hardlink is None:
344 if hardlink is None:
345 hardlink = (os.stat(src).st_dev ==
345 hardlink = (os.stat(src).st_dev ==
346 os.stat(os.path.dirname(dst)).st_dev)
346 os.stat(os.path.dirname(dst)).st_dev)
347
347
348 if os.path.isdir(src):
348 if os.path.isdir(src):
349 os.mkdir(dst)
349 os.mkdir(dst)
350 for name in os.listdir(src):
350 for name in os.listdir(src):
351 srcname = os.path.join(src, name)
351 srcname = os.path.join(src, name)
352 dstname = os.path.join(dst, name)
352 dstname = os.path.join(dst, name)
353 copyfiles(srcname, dstname, hardlink)
353 copyfiles(srcname, dstname, hardlink)
354 else:
354 else:
355 if hardlink:
355 if hardlink:
356 try:
356 try:
357 os_link(src, dst)
357 os_link(src, dst)
358 except:
358 except:
359 hardlink = False
359 hardlink = False
360 shutil.copy2(src, dst)
360 shutil.copy2(src, dst)
361 else:
361 else:
362 shutil.copy2(src, dst)
362 shutil.copy2(src, dst)
363
363
364 def opener(base):
364 def opener(base):
365 """
365 """
366 return a function that opens files relative to base
366 return a function that opens files relative to base
367
367
368 this function is used to hide the details of COW semantics and
368 this function is used to hide the details of COW semantics and
369 remote file access from higher level code.
369 remote file access from higher level code.
370 """
370 """
371 p = base
371 p = base
372
372
373 def mktempcopy(name):
373 def mktempcopy(name):
374 d, fn = os.path.split(name)
374 d, fn = os.path.split(name)
375 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
375 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
376 fp = os.fdopen(fd, "wb")
376 fp = os.fdopen(fd, "wb")
377 try:
377 try:
378 fp.write(file(name, "rb").read())
378 fp.write(file(name, "rb").read())
379 except:
379 except:
380 try: os.unlink(temp)
380 try: os.unlink(temp)
381 except: pass
381 except: pass
382 raise
382 raise
383 fp.close()
383 fp.close()
384 st = os.lstat(name)
384 st = os.lstat(name)
385 os.chmod(temp, st.st_mode)
385 os.chmod(temp, st.st_mode)
386 return temp
386 return temp
387
387
388 class atomicfile(file):
388 class atomicfile(file):
389 """the file will only be copied on close"""
389 """the file will only be copied on close"""
390 def __init__(self, name, mode, atomic=False):
390 def __init__(self, name, mode, atomic=False):
391 self.__name = name
391 self.__name = name
392 self.temp = mktempcopy(name)
392 self.temp = mktempcopy(name)
393 file.__init__(self, self.temp, mode)
393 file.__init__(self, self.temp, mode)
394 def close(self):
394 def close(self):
395 if not self.closed:
395 if not self.closed:
396 file.close(self)
396 file.close(self)
397 rename(self.temp, self.__name)
397 rename(self.temp, self.__name)
398 def __del__(self):
398 def __del__(self):
399 self.close()
399 self.close()
400
400
401 def o(path, mode="r", text=False, atomic=False):
401 def o(path, mode="r", text=False, atomic=False):
402 f = os.path.join(p, path)
402 f = os.path.join(p, path)
403
403
404 if not text:
404 if not text:
405 mode += "b" # for that other OS
405 mode += "b" # for that other OS
406
406
407 if mode[0] != "r":
407 if mode[0] != "r":
408 try:
408 try:
409 nlink = nlinks(f)
409 nlink = nlinks(f)
410 except OSError:
410 except OSError:
411 d = os.path.dirname(f)
411 d = os.path.dirname(f)
412 if not os.path.isdir(d):
412 if not os.path.isdir(d):
413 os.makedirs(d)
413 os.makedirs(d)
414 else:
414 else:
415 if atomic:
415 if atomic:
416 return atomicfile(f, mode)
416 return atomicfile(f, mode)
417 if nlink > 1:
417 if nlink > 1:
418 rename(mktempcopy(f), f)
418 rename(mktempcopy(f), f)
419 return file(f, mode)
419 return file(f, mode)
420
420
421 return o
421 return o
422
422
423 def _makelock_file(info, pathname):
423 def _makelock_file(info, pathname):
424 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
424 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
425 os.write(ld, info)
425 os.write(ld, info)
426 os.close(ld)
426 os.close(ld)
427
427
428 def _readlock_file(pathname):
428 def _readlock_file(pathname):
429 return file(pathname).read()
429 return file(pathname).read()
430
430
431 def nlinks(pathname):
431 def nlinks(pathname):
432 """Return number of hardlinks for the given file."""
432 """Return number of hardlinks for the given file."""
433 return os.stat(pathname).st_nlink
433 return os.stat(pathname).st_nlink
434
434
435 if hasattr(os, 'link'):
435 if hasattr(os, 'link'):
436 os_link = os.link
436 os_link = os.link
437 else:
437 else:
438 def os_link(src, dst):
438 def os_link(src, dst):
439 raise OSError(0, _("Hardlinks not supported"))
439 raise OSError(0, _("Hardlinks not supported"))
440
440
441 # Platform specific variants
441 # Platform specific variants
442 if os.name == 'nt':
442 if os.name == 'nt':
443 demandload(globals(), "msvcrt")
443 demandload(globals(), "msvcrt")
444 nulldev = 'NUL:'
444 nulldev = 'NUL:'
445
445
446 try:
446 try:
447 import win32api, win32process
447 import win32api, win32process
448 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
448 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
449 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
449 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
450
450
451 except ImportError:
451 except ImportError:
452 systemrc = r'c:\mercurial\mercurial.ini'
452 systemrc = r'c:\mercurial\mercurial.ini'
453 pass
453 pass
454
454
455 rcpath = (systemrc,
455 rcpath = (systemrc,
456 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
456 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
457
457
458 def parse_patch_output(output_line):
458 def parse_patch_output(output_line):
459 """parses the output produced by patch and returns the file name"""
459 """parses the output produced by patch and returns the file name"""
460 pf = output_line[14:]
460 pf = output_line[14:]
461 if pf[0] == '`':
461 if pf[0] == '`':
462 pf = pf[1:-1] # Remove the quotes
462 pf = pf[1:-1] # Remove the quotes
463 return pf
463 return pf
464
464
465 try: # ActivePython can create hard links using win32file module
465 try: # ActivePython can create hard links using win32file module
466 import win32file
466 import win32file
467
467
468 def os_link(src, dst): # NB will only succeed on NTFS
468 def os_link(src, dst): # NB will only succeed on NTFS
469 win32file.CreateHardLink(dst, src)
469 win32file.CreateHardLink(dst, src)
470
470
471 def nlinks(pathname):
471 def nlinks(pathname):
472 """Return number of hardlinks for the given file."""
472 """Return number of hardlinks for the given file."""
473 try:
473 try:
474 fh = win32file.CreateFile(pathname,
474 fh = win32file.CreateFile(pathname,
475 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
475 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
476 None, win32file.OPEN_EXISTING, 0, None)
476 None, win32file.OPEN_EXISTING, 0, None)
477 res = win32file.GetFileInformationByHandle(fh)
477 res = win32file.GetFileInformationByHandle(fh)
478 fh.Close()
478 fh.Close()
479 return res[7]
479 return res[7]
480 except:
480 except:
481 return os.stat(pathname).st_nlink
481 return os.stat(pathname).st_nlink
482
482
483 except ImportError:
483 except ImportError:
484 pass
484 pass
485
485
486 def is_exec(f, last):
486 def is_exec(f, last):
487 return last
487 return last
488
488
489 def set_exec(f, mode):
489 def set_exec(f, mode):
490 pass
490 pass
491
491
492 def set_binary(fd):
492 def set_binary(fd):
493 msvcrt.setmode(fd.fileno(), os.O_BINARY)
493 msvcrt.setmode(fd.fileno(), os.O_BINARY)
494
494
495 def pconvert(path):
495 def pconvert(path):
496 return path.replace("\\", "/")
496 return path.replace("\\", "/")
497
497
498 def localpath(path):
498 def localpath(path):
499 return path.replace('/', '\\')
499 return path.replace('/', '\\')
500
500
501 def normpath(path):
501 def normpath(path):
502 return pconvert(os.path.normpath(path))
502 return pconvert(os.path.normpath(path))
503
503
504 makelock = _makelock_file
504 makelock = _makelock_file
505 readlock = _readlock_file
505 readlock = _readlock_file
506
506
507 def explain_exit(code):
507 def explain_exit(code):
508 return _("exited with status %d") % code, code
508 return _("exited with status %d") % code, code
509
509
510 else:
510 else:
511 nulldev = '/dev/null'
511 nulldev = '/dev/null'
512
512
513 def rcfiles(path):
513 def rcfiles(path):
514 rcs = [os.path.join(path, 'hgrc')]
514 rcs = [os.path.join(path, 'hgrc')]
515 rcdir = os.path.join(path, 'hgrc.d')
515 rcdir = os.path.join(path, 'hgrc.d')
516 try:
516 try:
517 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
517 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
518 if f.endswith(".rc")])
518 if f.endswith(".rc")])
519 except OSError, inst: pass
519 except OSError, inst: pass
520 return rcs
520 return rcs
521 rcpath = rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial')
521 rcpath = rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial')
522 rcpath.extend(rcfiles('/etc/mercurial'))
522 rcpath.extend(rcfiles('/etc/mercurial'))
523 rcpath.append(os.path.expanduser('~/.hgrc'))
523 rcpath.append(os.path.expanduser('~/.hgrc'))
524 rcpath = [os.path.normpath(f) for f in rcpath]
524 rcpath = [os.path.normpath(f) for f in rcpath]
525
525
526 def parse_patch_output(output_line):
526 def parse_patch_output(output_line):
527 """parses the output produced by patch and returns the file name"""
527 """parses the output produced by patch and returns the file name"""
528 return output_line[14:]
528 pf = output_line[14:]
529 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
530 pf = pf[1:-1] # Remove the quotes
531 return pf
529
532
530 def is_exec(f, last):
533 def is_exec(f, last):
531 """check whether a file is executable"""
534 """check whether a file is executable"""
532 return (os.stat(f).st_mode & 0100 != 0)
535 return (os.stat(f).st_mode & 0100 != 0)
533
536
534 def set_exec(f, mode):
537 def set_exec(f, mode):
535 s = os.stat(f).st_mode
538 s = os.stat(f).st_mode
536 if (s & 0100 != 0) == mode:
539 if (s & 0100 != 0) == mode:
537 return
540 return
538 if mode:
541 if mode:
539 # Turn on +x for every +r bit when making a file executable
542 # Turn on +x for every +r bit when making a file executable
540 # and obey umask.
543 # and obey umask.
541 umask = os.umask(0)
544 umask = os.umask(0)
542 os.umask(umask)
545 os.umask(umask)
543 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
546 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
544 else:
547 else:
545 os.chmod(f, s & 0666)
548 os.chmod(f, s & 0666)
546
549
547 def set_binary(fd):
550 def set_binary(fd):
548 pass
551 pass
549
552
550 def pconvert(path):
553 def pconvert(path):
551 return path
554 return path
552
555
553 def localpath(path):
556 def localpath(path):
554 return path
557 return path
555
558
556 normpath = os.path.normpath
559 normpath = os.path.normpath
557
560
558 def makelock(info, pathname):
561 def makelock(info, pathname):
559 try:
562 try:
560 os.symlink(info, pathname)
563 os.symlink(info, pathname)
561 except OSError, why:
564 except OSError, why:
562 if why.errno == errno.EEXIST:
565 if why.errno == errno.EEXIST:
563 raise
566 raise
564 else:
567 else:
565 _makelock_file(info, pathname)
568 _makelock_file(info, pathname)
566
569
567 def readlock(pathname):
570 def readlock(pathname):
568 try:
571 try:
569 return os.readlink(pathname)
572 return os.readlink(pathname)
570 except OSError, why:
573 except OSError, why:
571 if why.errno == errno.EINVAL:
574 if why.errno == errno.EINVAL:
572 return _readlock_file(pathname)
575 return _readlock_file(pathname)
573 else:
576 else:
574 raise
577 raise
575
578
576 def explain_exit(code):
579 def explain_exit(code):
577 """return a 2-tuple (desc, code) describing a process's status"""
580 """return a 2-tuple (desc, code) describing a process's status"""
578 if os.WIFEXITED(code):
581 if os.WIFEXITED(code):
579 val = os.WEXITSTATUS(code)
582 val = os.WEXITSTATUS(code)
580 return _("exited with status %d") % val, val
583 return _("exited with status %d") % val, val
581 elif os.WIFSIGNALED(code):
584 elif os.WIFSIGNALED(code):
582 val = os.WTERMSIG(code)
585 val = os.WTERMSIG(code)
583 return _("killed by signal %d") % val, val
586 return _("killed by signal %d") % val, val
584 elif os.WIFSTOPPED(code):
587 elif os.WIFSTOPPED(code):
585 val = os.WSTOPSIG(code)
588 val = os.WSTOPSIG(code)
586 return _("stopped by signal %d") % val, val
589 return _("stopped by signal %d") % val, val
587 raise ValueError(_("invalid exit code"))
590 raise ValueError(_("invalid exit code"))
588
591
589 class chunkbuffer(object):
592 class chunkbuffer(object):
590 """Allow arbitrary sized chunks of data to be efficiently read from an
593 """Allow arbitrary sized chunks of data to be efficiently read from an
591 iterator over chunks of arbitrary size."""
594 iterator over chunks of arbitrary size."""
592
595
593 def __init__(self, in_iter, targetsize = 2**16):
596 def __init__(self, in_iter, targetsize = 2**16):
594 """in_iter is the iterator that's iterating over the input chunks.
597 """in_iter is the iterator that's iterating over the input chunks.
595 targetsize is how big a buffer to try to maintain."""
598 targetsize is how big a buffer to try to maintain."""
596 self.in_iter = iter(in_iter)
599 self.in_iter = iter(in_iter)
597 self.buf = ''
600 self.buf = ''
598 self.targetsize = int(targetsize)
601 self.targetsize = int(targetsize)
599 if self.targetsize <= 0:
602 if self.targetsize <= 0:
600 raise ValueError(_("targetsize must be greater than 0, was %d") %
603 raise ValueError(_("targetsize must be greater than 0, was %d") %
601 targetsize)
604 targetsize)
602 self.iterempty = False
605 self.iterempty = False
603
606
604 def fillbuf(self):
607 def fillbuf(self):
605 """Ignore target size; read every chunk from iterator until empty."""
608 """Ignore target size; read every chunk from iterator until empty."""
606 if not self.iterempty:
609 if not self.iterempty:
607 collector = cStringIO.StringIO()
610 collector = cStringIO.StringIO()
608 collector.write(self.buf)
611 collector.write(self.buf)
609 for ch in self.in_iter:
612 for ch in self.in_iter:
610 collector.write(ch)
613 collector.write(ch)
611 self.buf = collector.getvalue()
614 self.buf = collector.getvalue()
612 self.iterempty = True
615 self.iterempty = True
613
616
614 def read(self, l):
617 def read(self, l):
615 """Read L bytes of data from the iterator of chunks of data.
618 """Read L bytes of data from the iterator of chunks of data.
616 Returns less than L bytes if the iterator runs dry."""
619 Returns less than L bytes if the iterator runs dry."""
617 if l > len(self.buf) and not self.iterempty:
620 if l > len(self.buf) and not self.iterempty:
618 # Clamp to a multiple of self.targetsize
621 # Clamp to a multiple of self.targetsize
619 targetsize = self.targetsize * ((l // self.targetsize) + 1)
622 targetsize = self.targetsize * ((l // self.targetsize) + 1)
620 collector = cStringIO.StringIO()
623 collector = cStringIO.StringIO()
621 collector.write(self.buf)
624 collector.write(self.buf)
622 collected = len(self.buf)
625 collected = len(self.buf)
623 for chunk in self.in_iter:
626 for chunk in self.in_iter:
624 collector.write(chunk)
627 collector.write(chunk)
625 collected += len(chunk)
628 collected += len(chunk)
626 if collected >= targetsize:
629 if collected >= targetsize:
627 break
630 break
628 if collected < targetsize:
631 if collected < targetsize:
629 self.iterempty = True
632 self.iterempty = True
630 self.buf = collector.getvalue()
633 self.buf = collector.getvalue()
631 s, self.buf = self.buf[:l], buffer(self.buf, l)
634 s, self.buf = self.buf[:l], buffer(self.buf, l)
632 return s
635 return s
633
636
634 def filechunkiter(f, size = 65536):
637 def filechunkiter(f, size = 65536):
635 """Create a generator that produces all the data in the file size
638 """Create a generator that produces all the data in the file size
636 (default 65536) bytes at a time. Chunks may be less than size
639 (default 65536) bytes at a time. Chunks may be less than size
637 bytes if the chunk is the last chunk in the file, or the file is a
640 bytes if the chunk is the last chunk in the file, or the file is a
638 socket or some other type of file that sometimes reads less data
641 socket or some other type of file that sometimes reads less data
639 than is requested."""
642 than is requested."""
640 s = f.read(size)
643 s = f.read(size)
641 while len(s) > 0:
644 while len(s) > 0:
642 yield s
645 yield s
643 s = f.read(size)
646 s = f.read(size)
644
647
645 def makedate():
648 def makedate():
646 lt = time.localtime()
649 lt = time.localtime()
647 if lt[8] == 1 and time.daylight:
650 if lt[8] == 1 and time.daylight:
648 tz = time.altzone
651 tz = time.altzone
649 else:
652 else:
650 tz = time.timezone
653 tz = time.timezone
651 return time.mktime(lt), tz
654 return time.mktime(lt), tz
652
655
653 def datestr(date=None, format='%c'):
656 def datestr(date=None, format='%c'):
654 """represent a (unixtime, offset) tuple as a localized time.
657 """represent a (unixtime, offset) tuple as a localized time.
655 unixtime is seconds since the epoch, and offset is the time zone's
658 unixtime is seconds since the epoch, and offset is the time zone's
656 number of seconds away from UTC."""
659 number of seconds away from UTC."""
657 t, tz = date or makedate()
660 t, tz = date or makedate()
658 return ("%s %+03d%02d" %
661 return ("%s %+03d%02d" %
659 (time.strftime(format, time.gmtime(float(t) - tz)),
662 (time.strftime(format, time.gmtime(float(t) - tz)),
660 -tz / 3600,
663 -tz / 3600,
661 ((-tz % 3600) / 60)))
664 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now