##// END OF EJS Templates
copy the mode of the file when breaking hardlinks
Benoit Boissinot -
r1521:11a58d2c default
parent child Browse files
Show More
@@ -1,628 +1,630 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 def o(path, mode="r", text=False):
366 f = os.path.join(p, path)
366 f = os.path.join(p, path)
367
367
368 if not text:
368 if not text:
369 mode += "b" # for that other OS
369 mode += "b" # for that other OS
370
370
371 if mode[0] != "r":
371 if mode[0] != "r":
372 try:
372 try:
373 nlink = nlinks(f)
373 nlink = nlinks(f)
374 except OSError:
374 except OSError:
375 d = os.path.dirname(f)
375 d = os.path.dirname(f)
376 if not os.path.isdir(d):
376 if not os.path.isdir(d):
377 os.makedirs(d)
377 os.makedirs(d)
378 else:
378 else:
379 if nlink > 1:
379 if nlink > 1:
380 d, fn = os.path.split(f)
380 d, fn = os.path.split(f)
381 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
381 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
382 fp = os.fdopen(fd, "wb")
382 fp = os.fdopen(fd, "wb")
383 try:
383 try:
384 fp.write(file(f, "rb").read())
384 fp.write(file(f, "rb").read())
385 except:
385 except:
386 try: os.unlink(temp)
386 try: os.unlink(temp)
387 except: pass
387 except: pass
388 raise
388 raise
389 fp.close()
389 fp.close()
390 st = os.lstat(f)
391 os.chmod(temp, st.st_mode)
390 rename(temp, f)
392 rename(temp, f)
391
393
392 return file(f, mode)
394 return file(f, mode)
393
395
394 return o
396 return o
395
397
396 def _makelock_file(info, pathname):
398 def _makelock_file(info, pathname):
397 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
399 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
398 os.write(ld, info)
400 os.write(ld, info)
399 os.close(ld)
401 os.close(ld)
400
402
401 def _readlock_file(pathname):
403 def _readlock_file(pathname):
402 return file(pathname).read()
404 return file(pathname).read()
403
405
404 def nlinks(pathname):
406 def nlinks(pathname):
405 """Return number of hardlinks for the given file."""
407 """Return number of hardlinks for the given file."""
406 return os.stat(pathname).st_nlink
408 return os.stat(pathname).st_nlink
407
409
408 if hasattr(os, 'link'):
410 if hasattr(os, 'link'):
409 os_link = os.link
411 os_link = os.link
410 else:
412 else:
411 def os_link(src, dst):
413 def os_link(src, dst):
412 raise OSError(0, _("Hardlinks not supported"))
414 raise OSError(0, _("Hardlinks not supported"))
413
415
414 # Platform specific variants
416 # Platform specific variants
415 if os.name == 'nt':
417 if os.name == 'nt':
416 demandload(globals(), "msvcrt")
418 demandload(globals(), "msvcrt")
417 nulldev = 'NUL:'
419 nulldev = 'NUL:'
418
420
419 try:
421 try:
420 import win32api, win32process
422 import win32api, win32process
421 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
423 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
422 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
424 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
423
425
424 except ImportError:
426 except ImportError:
425 systemrc = r'c:\mercurial\mercurial.ini'
427 systemrc = r'c:\mercurial\mercurial.ini'
426 pass
428 pass
427
429
428 rcpath = (systemrc,
430 rcpath = (systemrc,
429 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
431 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
430
432
431 def parse_patch_output(output_line):
433 def parse_patch_output(output_line):
432 """parses the output produced by patch and returns the file name"""
434 """parses the output produced by patch and returns the file name"""
433 pf = output_line[14:]
435 pf = output_line[14:]
434 if pf[0] == '`':
436 if pf[0] == '`':
435 pf = pf[1:-1] # Remove the quotes
437 pf = pf[1:-1] # Remove the quotes
436 return pf
438 return pf
437
439
438 try: # ActivePython can create hard links using win32file module
440 try: # ActivePython can create hard links using win32file module
439 import win32file
441 import win32file
440
442
441 def os_link(src, dst): # NB will only succeed on NTFS
443 def os_link(src, dst): # NB will only succeed on NTFS
442 win32file.CreateHardLink(dst, src)
444 win32file.CreateHardLink(dst, src)
443
445
444 def nlinks(pathname):
446 def nlinks(pathname):
445 """Return number of hardlinks for the given file."""
447 """Return number of hardlinks for the given file."""
446 try:
448 try:
447 fh = win32file.CreateFile(pathname,
449 fh = win32file.CreateFile(pathname,
448 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
450 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
449 None, win32file.OPEN_EXISTING, 0, None)
451 None, win32file.OPEN_EXISTING, 0, None)
450 res = win32file.GetFileInformationByHandle(fh)
452 res = win32file.GetFileInformationByHandle(fh)
451 fh.Close()
453 fh.Close()
452 return res[7]
454 return res[7]
453 except:
455 except:
454 return os.stat(pathname).st_nlink
456 return os.stat(pathname).st_nlink
455
457
456 except ImportError:
458 except ImportError:
457 pass
459 pass
458
460
459 def is_exec(f, last):
461 def is_exec(f, last):
460 return last
462 return last
461
463
462 def set_exec(f, mode):
464 def set_exec(f, mode):
463 pass
465 pass
464
466
465 def set_binary(fd):
467 def set_binary(fd):
466 msvcrt.setmode(fd.fileno(), os.O_BINARY)
468 msvcrt.setmode(fd.fileno(), os.O_BINARY)
467
469
468 def pconvert(path):
470 def pconvert(path):
469 return path.replace("\\", "/")
471 return path.replace("\\", "/")
470
472
471 def localpath(path):
473 def localpath(path):
472 return path.replace('/', '\\')
474 return path.replace('/', '\\')
473
475
474 def normpath(path):
476 def normpath(path):
475 return pconvert(os.path.normpath(path))
477 return pconvert(os.path.normpath(path))
476
478
477 makelock = _makelock_file
479 makelock = _makelock_file
478 readlock = _readlock_file
480 readlock = _readlock_file
479
481
480 def explain_exit(code):
482 def explain_exit(code):
481 return _("exited with status %d") % code, code
483 return _("exited with status %d") % code, code
482
484
483 else:
485 else:
484 nulldev = '/dev/null'
486 nulldev = '/dev/null'
485
487
486 hgrcd = '/etc/mercurial/hgrc.d'
488 hgrcd = '/etc/mercurial/hgrc.d'
487 hgrcs = []
489 hgrcs = []
488 if os.path.isdir(hgrcd):
490 if os.path.isdir(hgrcd):
489 hgrcs = [f for f in os.listdir(hgrcd) if f.endswith(".rc")]
491 hgrcs = [f for f in os.listdir(hgrcd) if f.endswith(".rc")]
490 rcpath = map(os.path.normpath, hgrcs +
492 rcpath = map(os.path.normpath, hgrcs +
491 ['/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')])
493 ['/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')])
492
494
493 def parse_patch_output(output_line):
495 def parse_patch_output(output_line):
494 """parses the output produced by patch and returns the file name"""
496 """parses the output produced by patch and returns the file name"""
495 return output_line[14:]
497 return output_line[14:]
496
498
497 def is_exec(f, last):
499 def is_exec(f, last):
498 """check whether a file is executable"""
500 """check whether a file is executable"""
499 return (os.stat(f).st_mode & 0100 != 0)
501 return (os.stat(f).st_mode & 0100 != 0)
500
502
501 def set_exec(f, mode):
503 def set_exec(f, mode):
502 s = os.stat(f).st_mode
504 s = os.stat(f).st_mode
503 if (s & 0100 != 0) == mode:
505 if (s & 0100 != 0) == mode:
504 return
506 return
505 if mode:
507 if mode:
506 # Turn on +x for every +r bit when making a file executable
508 # Turn on +x for every +r bit when making a file executable
507 # and obey umask.
509 # and obey umask.
508 umask = os.umask(0)
510 umask = os.umask(0)
509 os.umask(umask)
511 os.umask(umask)
510 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
512 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
511 else:
513 else:
512 os.chmod(f, s & 0666)
514 os.chmod(f, s & 0666)
513
515
514 def set_binary(fd):
516 def set_binary(fd):
515 pass
517 pass
516
518
517 def pconvert(path):
519 def pconvert(path):
518 return path
520 return path
519
521
520 def localpath(path):
522 def localpath(path):
521 return path
523 return path
522
524
523 normpath = os.path.normpath
525 normpath = os.path.normpath
524
526
525 def makelock(info, pathname):
527 def makelock(info, pathname):
526 try:
528 try:
527 os.symlink(info, pathname)
529 os.symlink(info, pathname)
528 except OSError, why:
530 except OSError, why:
529 if why.errno == errno.EEXIST:
531 if why.errno == errno.EEXIST:
530 raise
532 raise
531 else:
533 else:
532 _makelock_file(info, pathname)
534 _makelock_file(info, pathname)
533
535
534 def readlock(pathname):
536 def readlock(pathname):
535 try:
537 try:
536 return os.readlink(pathname)
538 return os.readlink(pathname)
537 except OSError, why:
539 except OSError, why:
538 if why.errno == errno.EINVAL:
540 if why.errno == errno.EINVAL:
539 return _readlock_file(pathname)
541 return _readlock_file(pathname)
540 else:
542 else:
541 raise
543 raise
542
544
543 def explain_exit(code):
545 def explain_exit(code):
544 """return a 2-tuple (desc, code) describing a process's status"""
546 """return a 2-tuple (desc, code) describing a process's status"""
545 if os.WIFEXITED(code):
547 if os.WIFEXITED(code):
546 val = os.WEXITSTATUS(code)
548 val = os.WEXITSTATUS(code)
547 return _("exited with status %d") % val, val
549 return _("exited with status %d") % val, val
548 elif os.WIFSIGNALED(code):
550 elif os.WIFSIGNALED(code):
549 val = os.WTERMSIG(code)
551 val = os.WTERMSIG(code)
550 return _("killed by signal %d") % val, val
552 return _("killed by signal %d") % val, val
551 elif os.WIFSTOPPED(code):
553 elif os.WIFSTOPPED(code):
552 val = os.WSTOPSIG(code)
554 val = os.WSTOPSIG(code)
553 return _("stopped by signal %d") % val, val
555 return _("stopped by signal %d") % val, val
554 raise ValueError(_("invalid exit code"))
556 raise ValueError(_("invalid exit code"))
555
557
556 class chunkbuffer(object):
558 class chunkbuffer(object):
557 """Allow arbitrary sized chunks of data to be efficiently read from an
559 """Allow arbitrary sized chunks of data to be efficiently read from an
558 iterator over chunks of arbitrary size."""
560 iterator over chunks of arbitrary size."""
559
561
560 def __init__(self, in_iter, targetsize = 2**16):
562 def __init__(self, in_iter, targetsize = 2**16):
561 """in_iter is the iterator that's iterating over the input chunks.
563 """in_iter is the iterator that's iterating over the input chunks.
562 targetsize is how big a buffer to try to maintain."""
564 targetsize is how big a buffer to try to maintain."""
563 self.in_iter = iter(in_iter)
565 self.in_iter = iter(in_iter)
564 self.buf = ''
566 self.buf = ''
565 self.targetsize = int(targetsize)
567 self.targetsize = int(targetsize)
566 if self.targetsize <= 0:
568 if self.targetsize <= 0:
567 raise ValueError(_("targetsize must be greater than 0, was %d") %
569 raise ValueError(_("targetsize must be greater than 0, was %d") %
568 targetsize)
570 targetsize)
569 self.iterempty = False
571 self.iterempty = False
570
572
571 def fillbuf(self):
573 def fillbuf(self):
572 """Ignore target size; read every chunk from iterator until empty."""
574 """Ignore target size; read every chunk from iterator until empty."""
573 if not self.iterempty:
575 if not self.iterempty:
574 collector = cStringIO.StringIO()
576 collector = cStringIO.StringIO()
575 collector.write(self.buf)
577 collector.write(self.buf)
576 for ch in self.in_iter:
578 for ch in self.in_iter:
577 collector.write(ch)
579 collector.write(ch)
578 self.buf = collector.getvalue()
580 self.buf = collector.getvalue()
579 self.iterempty = True
581 self.iterempty = True
580
582
581 def read(self, l):
583 def read(self, l):
582 """Read L bytes of data from the iterator of chunks of data.
584 """Read L bytes of data from the iterator of chunks of data.
583 Returns less than L bytes if the iterator runs dry."""
585 Returns less than L bytes if the iterator runs dry."""
584 if l > len(self.buf) and not self.iterempty:
586 if l > len(self.buf) and not self.iterempty:
585 # Clamp to a multiple of self.targetsize
587 # Clamp to a multiple of self.targetsize
586 targetsize = self.targetsize * ((l // self.targetsize) + 1)
588 targetsize = self.targetsize * ((l // self.targetsize) + 1)
587 collector = cStringIO.StringIO()
589 collector = cStringIO.StringIO()
588 collector.write(self.buf)
590 collector.write(self.buf)
589 collected = len(self.buf)
591 collected = len(self.buf)
590 for chunk in self.in_iter:
592 for chunk in self.in_iter:
591 collector.write(chunk)
593 collector.write(chunk)
592 collected += len(chunk)
594 collected += len(chunk)
593 if collected >= targetsize:
595 if collected >= targetsize:
594 break
596 break
595 if collected < targetsize:
597 if collected < targetsize:
596 self.iterempty = True
598 self.iterempty = True
597 self.buf = collector.getvalue()
599 self.buf = collector.getvalue()
598 s, self.buf = self.buf[:l], buffer(self.buf, l)
600 s, self.buf = self.buf[:l], buffer(self.buf, l)
599 return s
601 return s
600
602
601 def filechunkiter(f, size = 65536):
603 def filechunkiter(f, size = 65536):
602 """Create a generator that produces all the data in the file size
604 """Create a generator that produces all the data in the file size
603 (default 65536) bytes at a time. Chunks may be less than size
605 (default 65536) bytes at a time. Chunks may be less than size
604 bytes if the chunk is the last chunk in the file, or the file is a
606 bytes if the chunk is the last chunk in the file, or the file is a
605 socket or some other type of file that sometimes reads less data
607 socket or some other type of file that sometimes reads less data
606 than is requested."""
608 than is requested."""
607 s = f.read(size)
609 s = f.read(size)
608 while len(s) > 0:
610 while len(s) > 0:
609 yield s
611 yield s
610 s = f.read(size)
612 s = f.read(size)
611
613
612 def makedate():
614 def makedate():
613 lt = time.localtime()
615 lt = time.localtime()
614 if lt[8] == 1 and time.daylight:
616 if lt[8] == 1 and time.daylight:
615 tz = time.altzone
617 tz = time.altzone
616 else:
618 else:
617 tz = time.timezone
619 tz = time.timezone
618 return time.mktime(lt), tz
620 return time.mktime(lt), tz
619
621
620 def datestr(date=None, format='%c'):
622 def datestr(date=None, format='%c'):
621 """represent a (unixtime, offset) tuple as a localized time.
623 """represent a (unixtime, offset) tuple as a localized time.
622 unixtime is seconds since the epoch, and offset is the time zone's
624 unixtime is seconds since the epoch, and offset is the time zone's
623 number of seconds away from UTC."""
625 number of seconds away from UTC."""
624 t, tz = date or makedate()
626 t, tz = date or makedate()
625 return ("%s %+03d%02d" %
627 return ("%s %+03d%02d" %
626 (time.strftime(format, time.gmtime(float(t) - tz)),
628 (time.strftime(format, time.gmtime(float(t) - tz)),
627 -tz / 3600,
629 -tz / 3600,
628 ((-tz % 3600) / 60)))
630 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now