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