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