##// END OF EJS Templates
clean up lee's windows testpid fix.
Vadim Gelfer -
r2025:581d9a8b default
parent child Browse files
Show More
@@ -1,818 +1,816
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(), "cStringIO errno popen2 re shutil sys tempfile")
16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
17 demandload(globals(), "threading time")
17 demandload(globals(), "threading time")
18
18
19 def pipefilter(s, cmd):
19 def pipefilter(s, cmd):
20 '''filter string S through command CMD, returning its output'''
20 '''filter string S through command CMD, returning its output'''
21 (pout, pin) = popen2.popen2(cmd, -1, 'b')
21 (pout, pin) = popen2.popen2(cmd, -1, 'b')
22 def writer():
22 def writer():
23 pin.write(s)
23 pin.write(s)
24 pin.close()
24 pin.close()
25
25
26 # we should use select instead on UNIX, but this will work on most
26 # we should use select instead on UNIX, but this will work on most
27 # systems, including Windows
27 # systems, including Windows
28 w = threading.Thread(target=writer)
28 w = threading.Thread(target=writer)
29 w.start()
29 w.start()
30 f = pout.read()
30 f = pout.read()
31 pout.close()
31 pout.close()
32 w.join()
32 w.join()
33 return f
33 return f
34
34
35 def tempfilter(s, cmd):
35 def tempfilter(s, cmd):
36 '''filter string S through a pair of temporary files with CMD.
36 '''filter string S through a pair of temporary files with CMD.
37 CMD is used as a template to create the real command to be run,
37 CMD is used as a template to create the real command to be run,
38 with the strings INFILE and OUTFILE replaced by the real names of
38 with the strings INFILE and OUTFILE replaced by the real names of
39 the temporary files generated.'''
39 the temporary files generated.'''
40 inname, outname = None, None
40 inname, outname = None, None
41 try:
41 try:
42 infd, inname = tempfile.mkstemp(prefix='hgfin')
42 infd, inname = tempfile.mkstemp(prefix='hgfin')
43 fp = os.fdopen(infd, 'wb')
43 fp = os.fdopen(infd, 'wb')
44 fp.write(s)
44 fp.write(s)
45 fp.close()
45 fp.close()
46 outfd, outname = tempfile.mkstemp(prefix='hgfout')
46 outfd, outname = tempfile.mkstemp(prefix='hgfout')
47 os.close(outfd)
47 os.close(outfd)
48 cmd = cmd.replace('INFILE', inname)
48 cmd = cmd.replace('INFILE', inname)
49 cmd = cmd.replace('OUTFILE', outname)
49 cmd = cmd.replace('OUTFILE', outname)
50 code = os.system(cmd)
50 code = os.system(cmd)
51 if code: raise Abort(_("command '%s' failed: %s") %
51 if code: raise Abort(_("command '%s' failed: %s") %
52 (cmd, explain_exit(code)))
52 (cmd, explain_exit(code)))
53 return open(outname, 'rb').read()
53 return open(outname, 'rb').read()
54 finally:
54 finally:
55 try:
55 try:
56 if inname: os.unlink(inname)
56 if inname: os.unlink(inname)
57 except: pass
57 except: pass
58 try:
58 try:
59 if outname: os.unlink(outname)
59 if outname: os.unlink(outname)
60 except: pass
60 except: pass
61
61
62 filtertable = {
62 filtertable = {
63 'tempfile:': tempfilter,
63 'tempfile:': tempfilter,
64 'pipe:': pipefilter,
64 'pipe:': pipefilter,
65 }
65 }
66
66
67 def filter(s, cmd):
67 def filter(s, cmd):
68 "filter a string through a command that transforms its input to its output"
68 "filter a string through a command that transforms its input to its output"
69 for name, fn in filtertable.iteritems():
69 for name, fn in filtertable.iteritems():
70 if cmd.startswith(name):
70 if cmd.startswith(name):
71 return fn(s, cmd[len(name):].lstrip())
71 return fn(s, cmd[len(name):].lstrip())
72 return pipefilter(s, cmd)
72 return pipefilter(s, cmd)
73
73
74 def patch(strip, patchname, ui):
74 def patch(strip, patchname, ui):
75 """apply the patch <patchname> to the working directory.
75 """apply the patch <patchname> to the working directory.
76 a list of patched files is returned"""
76 a list of patched files is returned"""
77 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
77 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
78 files = {}
78 files = {}
79 for line in fp:
79 for line in fp:
80 line = line.rstrip()
80 line = line.rstrip()
81 ui.status("%s\n" % line)
81 ui.status("%s\n" % line)
82 if line.startswith('patching file '):
82 if line.startswith('patching file '):
83 pf = parse_patch_output(line)
83 pf = parse_patch_output(line)
84 files.setdefault(pf, 1)
84 files.setdefault(pf, 1)
85 code = fp.close()
85 code = fp.close()
86 if code:
86 if code:
87 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
87 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
88 return files.keys()
88 return files.keys()
89
89
90 def binary(s):
90 def binary(s):
91 """return true if a string is binary data using diff's heuristic"""
91 """return true if a string is binary data using diff's heuristic"""
92 if s and '\0' in s[:4096]:
92 if s and '\0' in s[:4096]:
93 return True
93 return True
94 return False
94 return False
95
95
96 def unique(g):
96 def unique(g):
97 """return the uniq elements of iterable g"""
97 """return the uniq elements of iterable g"""
98 seen = {}
98 seen = {}
99 for f in g:
99 for f in g:
100 if f not in seen:
100 if f not in seen:
101 seen[f] = 1
101 seen[f] = 1
102 yield f
102 yield f
103
103
104 class Abort(Exception):
104 class Abort(Exception):
105 """Raised if a command needs to print an error and exit."""
105 """Raised if a command needs to print an error and exit."""
106
106
107 def always(fn): return True
107 def always(fn): return True
108 def never(fn): return False
108 def never(fn): return False
109
109
110 def patkind(name, dflt_pat='glob'):
110 def patkind(name, dflt_pat='glob'):
111 """Split a string into an optional pattern kind prefix and the
111 """Split a string into an optional pattern kind prefix and the
112 actual pattern."""
112 actual pattern."""
113 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
113 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
114 if name.startswith(prefix + ':'): return name.split(':', 1)
114 if name.startswith(prefix + ':'): return name.split(':', 1)
115 return dflt_pat, name
115 return dflt_pat, name
116
116
117 def globre(pat, head='^', tail='$'):
117 def globre(pat, head='^', tail='$'):
118 "convert a glob pattern into a regexp"
118 "convert a glob pattern into a regexp"
119 i, n = 0, len(pat)
119 i, n = 0, len(pat)
120 res = ''
120 res = ''
121 group = False
121 group = False
122 def peek(): return i < n and pat[i]
122 def peek(): return i < n and pat[i]
123 while i < n:
123 while i < n:
124 c = pat[i]
124 c = pat[i]
125 i = i+1
125 i = i+1
126 if c == '*':
126 if c == '*':
127 if peek() == '*':
127 if peek() == '*':
128 i += 1
128 i += 1
129 res += '.*'
129 res += '.*'
130 else:
130 else:
131 res += '[^/]*'
131 res += '[^/]*'
132 elif c == '?':
132 elif c == '?':
133 res += '.'
133 res += '.'
134 elif c == '[':
134 elif c == '[':
135 j = i
135 j = i
136 if j < n and pat[j] in '!]':
136 if j < n and pat[j] in '!]':
137 j += 1
137 j += 1
138 while j < n and pat[j] != ']':
138 while j < n and pat[j] != ']':
139 j += 1
139 j += 1
140 if j >= n:
140 if j >= n:
141 res += '\\['
141 res += '\\['
142 else:
142 else:
143 stuff = pat[i:j].replace('\\','\\\\')
143 stuff = pat[i:j].replace('\\','\\\\')
144 i = j + 1
144 i = j + 1
145 if stuff[0] == '!':
145 if stuff[0] == '!':
146 stuff = '^' + stuff[1:]
146 stuff = '^' + stuff[1:]
147 elif stuff[0] == '^':
147 elif stuff[0] == '^':
148 stuff = '\\' + stuff
148 stuff = '\\' + stuff
149 res = '%s[%s]' % (res, stuff)
149 res = '%s[%s]' % (res, stuff)
150 elif c == '{':
150 elif c == '{':
151 group = True
151 group = True
152 res += '(?:'
152 res += '(?:'
153 elif c == '}' and group:
153 elif c == '}' and group:
154 res += ')'
154 res += ')'
155 group = False
155 group = False
156 elif c == ',' and group:
156 elif c == ',' and group:
157 res += '|'
157 res += '|'
158 elif c == '\\':
158 elif c == '\\':
159 p = peek()
159 p = peek()
160 if p:
160 if p:
161 i += 1
161 i += 1
162 res += re.escape(p)
162 res += re.escape(p)
163 else:
163 else:
164 res += re.escape(c)
164 res += re.escape(c)
165 else:
165 else:
166 res += re.escape(c)
166 res += re.escape(c)
167 return head + res + tail
167 return head + res + tail
168
168
169 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
169 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
170
170
171 def pathto(n1, n2):
171 def pathto(n1, n2):
172 '''return the relative path from one place to another.
172 '''return the relative path from one place to another.
173 this returns a path in the form used by the local filesystem, not hg.'''
173 this returns a path in the form used by the local filesystem, not hg.'''
174 if not n1: return localpath(n2)
174 if not n1: return localpath(n2)
175 a, b = n1.split('/'), n2.split('/')
175 a, b = n1.split('/'), n2.split('/')
176 a.reverse()
176 a.reverse()
177 b.reverse()
177 b.reverse()
178 while a and b and a[-1] == b[-1]:
178 while a and b and a[-1] == b[-1]:
179 a.pop()
179 a.pop()
180 b.pop()
180 b.pop()
181 b.reverse()
181 b.reverse()
182 return os.sep.join((['..'] * len(a)) + b)
182 return os.sep.join((['..'] * len(a)) + b)
183
183
184 def canonpath(root, cwd, myname):
184 def canonpath(root, cwd, myname):
185 """return the canonical path of myname, given cwd and root"""
185 """return the canonical path of myname, given cwd and root"""
186 if root == os.sep:
186 if root == os.sep:
187 rootsep = os.sep
187 rootsep = os.sep
188 else:
188 else:
189 rootsep = root + os.sep
189 rootsep = root + os.sep
190 name = myname
190 name = myname
191 if not name.startswith(os.sep):
191 if not name.startswith(os.sep):
192 name = os.path.join(root, cwd, name)
192 name = os.path.join(root, cwd, name)
193 name = os.path.normpath(name)
193 name = os.path.normpath(name)
194 if name.startswith(rootsep):
194 if name.startswith(rootsep):
195 name = name[len(rootsep):]
195 name = name[len(rootsep):]
196 audit_path(name)
196 audit_path(name)
197 return pconvert(name)
197 return pconvert(name)
198 elif name == root:
198 elif name == root:
199 return ''
199 return ''
200 else:
200 else:
201 raise Abort('%s not under root' % myname)
201 raise Abort('%s not under root' % myname)
202
202
203 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
203 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
204 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
204 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
205
205
206 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
206 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
207 if os.name == 'nt':
207 if os.name == 'nt':
208 dflt_pat = 'glob'
208 dflt_pat = 'glob'
209 else:
209 else:
210 dflt_pat = 'relpath'
210 dflt_pat = 'relpath'
211 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
211 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
212
212
213 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
213 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
214 """build a function to match a set of file patterns
214 """build a function to match a set of file patterns
215
215
216 arguments:
216 arguments:
217 canonroot - the canonical root of the tree you're matching against
217 canonroot - the canonical root of the tree you're matching against
218 cwd - the current working directory, if relevant
218 cwd - the current working directory, if relevant
219 names - patterns to find
219 names - patterns to find
220 inc - patterns to include
220 inc - patterns to include
221 exc - patterns to exclude
221 exc - patterns to exclude
222 head - a regex to prepend to patterns to control whether a match is rooted
222 head - a regex to prepend to patterns to control whether a match is rooted
223
223
224 a pattern is one of:
224 a pattern is one of:
225 'glob:<rooted glob>'
225 'glob:<rooted glob>'
226 're:<rooted regexp>'
226 're:<rooted regexp>'
227 'path:<rooted path>'
227 'path:<rooted path>'
228 'relglob:<relative glob>'
228 'relglob:<relative glob>'
229 'relpath:<relative path>'
229 'relpath:<relative path>'
230 'relre:<relative regexp>'
230 'relre:<relative regexp>'
231 '<rooted path or regexp>'
231 '<rooted path or regexp>'
232
232
233 returns:
233 returns:
234 a 3-tuple containing
234 a 3-tuple containing
235 - list of explicit non-pattern names passed in
235 - list of explicit non-pattern names passed in
236 - a bool match(filename) function
236 - a bool match(filename) function
237 - a bool indicating if any patterns were passed in
237 - a bool indicating if any patterns were passed in
238
238
239 todo:
239 todo:
240 make head regex a rooted bool
240 make head regex a rooted bool
241 """
241 """
242
242
243 def contains_glob(name):
243 def contains_glob(name):
244 for c in name:
244 for c in name:
245 if c in _globchars: return True
245 if c in _globchars: return True
246 return False
246 return False
247
247
248 def regex(kind, name, tail):
248 def regex(kind, name, tail):
249 '''convert a pattern into a regular expression'''
249 '''convert a pattern into a regular expression'''
250 if kind == 're':
250 if kind == 're':
251 return name
251 return name
252 elif kind == 'path':
252 elif kind == 'path':
253 return '^' + re.escape(name) + '(?:/|$)'
253 return '^' + re.escape(name) + '(?:/|$)'
254 elif kind == 'relglob':
254 elif kind == 'relglob':
255 return head + globre(name, '(?:|.*/)', tail)
255 return head + globre(name, '(?:|.*/)', tail)
256 elif kind == 'relpath':
256 elif kind == 'relpath':
257 return head + re.escape(name) + tail
257 return head + re.escape(name) + tail
258 elif kind == 'relre':
258 elif kind == 'relre':
259 if name.startswith('^'):
259 if name.startswith('^'):
260 return name
260 return name
261 return '.*' + name
261 return '.*' + name
262 return head + globre(name, '', tail)
262 return head + globre(name, '', tail)
263
263
264 def matchfn(pats, tail):
264 def matchfn(pats, tail):
265 """build a matching function from a set of patterns"""
265 """build a matching function from a set of patterns"""
266 if not pats:
266 if not pats:
267 return
267 return
268 matches = []
268 matches = []
269 for k, p in pats:
269 for k, p in pats:
270 try:
270 try:
271 pat = '(?:%s)' % regex(k, p, tail)
271 pat = '(?:%s)' % regex(k, p, tail)
272 matches.append(re.compile(pat).match)
272 matches.append(re.compile(pat).match)
273 except re.error:
273 except re.error:
274 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
274 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
275 else: raise Abort("invalid pattern (%s): %s" % (k, p))
275 else: raise Abort("invalid pattern (%s): %s" % (k, p))
276
276
277 def buildfn(text):
277 def buildfn(text):
278 for m in matches:
278 for m in matches:
279 r = m(text)
279 r = m(text)
280 if r:
280 if r:
281 return r
281 return r
282
282
283 return buildfn
283 return buildfn
284
284
285 def globprefix(pat):
285 def globprefix(pat):
286 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
286 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
287 root = []
287 root = []
288 for p in pat.split(os.sep):
288 for p in pat.split(os.sep):
289 if contains_glob(p): break
289 if contains_glob(p): break
290 root.append(p)
290 root.append(p)
291 return '/'.join(root)
291 return '/'.join(root)
292
292
293 pats = []
293 pats = []
294 files = []
294 files = []
295 roots = []
295 roots = []
296 for kind, name in [patkind(p, dflt_pat) for p in names]:
296 for kind, name in [patkind(p, dflt_pat) for p in names]:
297 if kind in ('glob', 'relpath'):
297 if kind in ('glob', 'relpath'):
298 name = canonpath(canonroot, cwd, name)
298 name = canonpath(canonroot, cwd, name)
299 if name == '':
299 if name == '':
300 kind, name = 'glob', '**'
300 kind, name = 'glob', '**'
301 if kind in ('glob', 'path', 're'):
301 if kind in ('glob', 'path', 're'):
302 pats.append((kind, name))
302 pats.append((kind, name))
303 if kind == 'glob':
303 if kind == 'glob':
304 root = globprefix(name)
304 root = globprefix(name)
305 if root: roots.append(root)
305 if root: roots.append(root)
306 elif kind == 'relpath':
306 elif kind == 'relpath':
307 files.append((kind, name))
307 files.append((kind, name))
308 roots.append(name)
308 roots.append(name)
309
309
310 patmatch = matchfn(pats, '$') or always
310 patmatch = matchfn(pats, '$') or always
311 filematch = matchfn(files, '(?:/|$)') or always
311 filematch = matchfn(files, '(?:/|$)') or always
312 incmatch = always
312 incmatch = always
313 if inc:
313 if inc:
314 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
314 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
315 excmatch = lambda fn: False
315 excmatch = lambda fn: False
316 if exc:
316 if exc:
317 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
317 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
318
318
319 return (roots,
319 return (roots,
320 lambda fn: (incmatch(fn) and not excmatch(fn) and
320 lambda fn: (incmatch(fn) and not excmatch(fn) and
321 (fn.endswith('/') or
321 (fn.endswith('/') or
322 (not pats and not files) or
322 (not pats and not files) or
323 (pats and patmatch(fn)) or
323 (pats and patmatch(fn)) or
324 (files and filematch(fn)))),
324 (files and filematch(fn)))),
325 (inc or exc or (pats and pats != [('glob', '**')])) and True)
325 (inc or exc or (pats and pats != [('glob', '**')])) and True)
326
326
327 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
327 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
328 '''enhanced shell command execution.
328 '''enhanced shell command execution.
329 run with environment maybe modified, maybe in different dir.
329 run with environment maybe modified, maybe in different dir.
330
330
331 if command fails and onerr is None, return status. if ui object,
331 if command fails and onerr is None, return status. if ui object,
332 print error message and return status, else raise onerr object as
332 print error message and return status, else raise onerr object as
333 exception.'''
333 exception.'''
334 oldenv = {}
334 oldenv = {}
335 for k in environ:
335 for k in environ:
336 oldenv[k] = os.environ.get(k)
336 oldenv[k] = os.environ.get(k)
337 if cwd is not None:
337 if cwd is not None:
338 oldcwd = os.getcwd()
338 oldcwd = os.getcwd()
339 try:
339 try:
340 for k, v in environ.iteritems():
340 for k, v in environ.iteritems():
341 os.environ[k] = str(v)
341 os.environ[k] = str(v)
342 if cwd is not None and oldcwd != cwd:
342 if cwd is not None and oldcwd != cwd:
343 os.chdir(cwd)
343 os.chdir(cwd)
344 rc = os.system(cmd)
344 rc = os.system(cmd)
345 if rc and onerr:
345 if rc and onerr:
346 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
346 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
347 explain_exit(rc)[0])
347 explain_exit(rc)[0])
348 if errprefix:
348 if errprefix:
349 errmsg = '%s: %s' % (errprefix, errmsg)
349 errmsg = '%s: %s' % (errprefix, errmsg)
350 try:
350 try:
351 onerr.warn(errmsg + '\n')
351 onerr.warn(errmsg + '\n')
352 except AttributeError:
352 except AttributeError:
353 raise onerr(errmsg)
353 raise onerr(errmsg)
354 return rc
354 return rc
355 finally:
355 finally:
356 for k, v in oldenv.iteritems():
356 for k, v in oldenv.iteritems():
357 if v is None:
357 if v is None:
358 del os.environ[k]
358 del os.environ[k]
359 else:
359 else:
360 os.environ[k] = v
360 os.environ[k] = v
361 if cwd is not None and oldcwd != cwd:
361 if cwd is not None and oldcwd != cwd:
362 os.chdir(oldcwd)
362 os.chdir(oldcwd)
363
363
364 def rename(src, dst):
364 def rename(src, dst):
365 """forcibly rename a file"""
365 """forcibly rename a file"""
366 try:
366 try:
367 os.rename(src, dst)
367 os.rename(src, dst)
368 except:
368 except:
369 os.unlink(dst)
369 os.unlink(dst)
370 os.rename(src, dst)
370 os.rename(src, dst)
371
371
372 def unlink(f):
372 def unlink(f):
373 """unlink and remove the directory if it is empty"""
373 """unlink and remove the directory if it is empty"""
374 os.unlink(f)
374 os.unlink(f)
375 # try removing directories that might now be empty
375 # try removing directories that might now be empty
376 try: os.removedirs(os.path.dirname(f))
376 try: os.removedirs(os.path.dirname(f))
377 except: pass
377 except: pass
378
378
379 def copyfiles(src, dst, hardlink=None):
379 def copyfiles(src, dst, hardlink=None):
380 """Copy a directory tree using hardlinks if possible"""
380 """Copy a directory tree using hardlinks if possible"""
381
381
382 if hardlink is None:
382 if hardlink is None:
383 hardlink = (os.stat(src).st_dev ==
383 hardlink = (os.stat(src).st_dev ==
384 os.stat(os.path.dirname(dst)).st_dev)
384 os.stat(os.path.dirname(dst)).st_dev)
385
385
386 if os.path.isdir(src):
386 if os.path.isdir(src):
387 os.mkdir(dst)
387 os.mkdir(dst)
388 for name in os.listdir(src):
388 for name in os.listdir(src):
389 srcname = os.path.join(src, name)
389 srcname = os.path.join(src, name)
390 dstname = os.path.join(dst, name)
390 dstname = os.path.join(dst, name)
391 copyfiles(srcname, dstname, hardlink)
391 copyfiles(srcname, dstname, hardlink)
392 else:
392 else:
393 if hardlink:
393 if hardlink:
394 try:
394 try:
395 os_link(src, dst)
395 os_link(src, dst)
396 except:
396 except:
397 hardlink = False
397 hardlink = False
398 shutil.copy(src, dst)
398 shutil.copy(src, dst)
399 else:
399 else:
400 shutil.copy(src, dst)
400 shutil.copy(src, dst)
401
401
402 def audit_path(path):
402 def audit_path(path):
403 """Abort if path contains dangerous components"""
403 """Abort if path contains dangerous components"""
404 parts = os.path.normcase(path).split(os.sep)
404 parts = os.path.normcase(path).split(os.sep)
405 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
405 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
406 or os.pardir in parts):
406 or os.pardir in parts):
407 raise Abort(_("path contains illegal component: %s\n") % path)
407 raise Abort(_("path contains illegal component: %s\n") % path)
408
408
409 def opener(base, audit=True):
409 def opener(base, audit=True):
410 """
410 """
411 return a function that opens files relative to base
411 return a function that opens files relative to base
412
412
413 this function is used to hide the details of COW semantics and
413 this function is used to hide the details of COW semantics and
414 remote file access from higher level code.
414 remote file access from higher level code.
415 """
415 """
416 p = base
416 p = base
417 audit_p = audit
417 audit_p = audit
418
418
419 def mktempcopy(name):
419 def mktempcopy(name):
420 d, fn = os.path.split(name)
420 d, fn = os.path.split(name)
421 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
421 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
422 fp = os.fdopen(fd, "wb")
422 fp = os.fdopen(fd, "wb")
423 try:
423 try:
424 fp.write(file(name, "rb").read())
424 fp.write(file(name, "rb").read())
425 except:
425 except:
426 try: os.unlink(temp)
426 try: os.unlink(temp)
427 except: pass
427 except: pass
428 raise
428 raise
429 fp.close()
429 fp.close()
430 st = os.lstat(name)
430 st = os.lstat(name)
431 os.chmod(temp, st.st_mode)
431 os.chmod(temp, st.st_mode)
432 return temp
432 return temp
433
433
434 class atomicfile(file):
434 class atomicfile(file):
435 """the file will only be copied on close"""
435 """the file will only be copied on close"""
436 def __init__(self, name, mode, atomic=False):
436 def __init__(self, name, mode, atomic=False):
437 self.__name = name
437 self.__name = name
438 self.temp = mktempcopy(name)
438 self.temp = mktempcopy(name)
439 file.__init__(self, self.temp, mode)
439 file.__init__(self, self.temp, mode)
440 def close(self):
440 def close(self):
441 if not self.closed:
441 if not self.closed:
442 file.close(self)
442 file.close(self)
443 rename(self.temp, self.__name)
443 rename(self.temp, self.__name)
444 def __del__(self):
444 def __del__(self):
445 self.close()
445 self.close()
446
446
447 def o(path, mode="r", text=False, atomic=False):
447 def o(path, mode="r", text=False, atomic=False):
448 if audit_p:
448 if audit_p:
449 audit_path(path)
449 audit_path(path)
450 f = os.path.join(p, path)
450 f = os.path.join(p, path)
451
451
452 if not text:
452 if not text:
453 mode += "b" # for that other OS
453 mode += "b" # for that other OS
454
454
455 if mode[0] != "r":
455 if mode[0] != "r":
456 try:
456 try:
457 nlink = nlinks(f)
457 nlink = nlinks(f)
458 except OSError:
458 except OSError:
459 d = os.path.dirname(f)
459 d = os.path.dirname(f)
460 if not os.path.isdir(d):
460 if not os.path.isdir(d):
461 os.makedirs(d)
461 os.makedirs(d)
462 else:
462 else:
463 if atomic:
463 if atomic:
464 return atomicfile(f, mode)
464 return atomicfile(f, mode)
465 if nlink > 1:
465 if nlink > 1:
466 rename(mktempcopy(f), f)
466 rename(mktempcopy(f), f)
467 return file(f, mode)
467 return file(f, mode)
468
468
469 return o
469 return o
470
470
471 def _makelock_file(info, pathname):
471 def _makelock_file(info, pathname):
472 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
472 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
473 os.write(ld, info)
473 os.write(ld, info)
474 os.close(ld)
474 os.close(ld)
475
475
476 def _readlock_file(pathname):
476 def _readlock_file(pathname):
477 return file(pathname).read()
477 return file(pathname).read()
478
478
479 def nlinks(pathname):
479 def nlinks(pathname):
480 """Return number of hardlinks for the given file."""
480 """Return number of hardlinks for the given file."""
481 return os.stat(pathname).st_nlink
481 return os.stat(pathname).st_nlink
482
482
483 if hasattr(os, 'link'):
483 if hasattr(os, 'link'):
484 os_link = os.link
484 os_link = os.link
485 else:
485 else:
486 def os_link(src, dst):
486 def os_link(src, dst):
487 raise OSError(0, _("Hardlinks not supported"))
487 raise OSError(0, _("Hardlinks not supported"))
488
488
489 # Platform specific variants
489 # Platform specific variants
490 if os.name == 'nt':
490 if os.name == 'nt':
491 demandload(globals(), "msvcrt")
491 demandload(globals(), "msvcrt")
492 nulldev = 'NUL:'
492 nulldev = 'NUL:'
493
493
494 class winstdout:
494 class winstdout:
495 '''stdout on windows misbehaves if sent through a pipe'''
495 '''stdout on windows misbehaves if sent through a pipe'''
496
496
497 def __init__(self, fp):
497 def __init__(self, fp):
498 self.fp = fp
498 self.fp = fp
499
499
500 def __getattr__(self, key):
500 def __getattr__(self, key):
501 return getattr(self.fp, key)
501 return getattr(self.fp, key)
502
502
503 def close(self):
503 def close(self):
504 try:
504 try:
505 self.fp.close()
505 self.fp.close()
506 except: pass
506 except: pass
507
507
508 def write(self, s):
508 def write(self, s):
509 try:
509 try:
510 return self.fp.write(s)
510 return self.fp.write(s)
511 except IOError, inst:
511 except IOError, inst:
512 if inst.errno != 0: raise
512 if inst.errno != 0: raise
513 self.close()
513 self.close()
514 raise IOError(errno.EPIPE, 'Broken pipe')
514 raise IOError(errno.EPIPE, 'Broken pipe')
515
515
516 sys.stdout = winstdout(sys.stdout)
516 sys.stdout = winstdout(sys.stdout)
517
517
518 def os_rcpath():
518 def os_rcpath():
519 '''return default os-specific hgrc search path'''
519 '''return default os-specific hgrc search path'''
520 try:
520 try:
521 import win32api, win32process
521 import win32api, win32process
522 proc = win32api.GetCurrentProcess()
522 proc = win32api.GetCurrentProcess()
523 filename = win32process.GetModuleFileNameEx(proc, 0)
523 filename = win32process.GetModuleFileNameEx(proc, 0)
524 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
524 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
525 except ImportError:
525 except ImportError:
526 systemrc = r'c:\mercurial\mercurial.ini'
526 systemrc = r'c:\mercurial\mercurial.ini'
527
527
528 return [systemrc,
528 return [systemrc,
529 os.path.join(os.path.expanduser('~'), 'mercurial.ini')]
529 os.path.join(os.path.expanduser('~'), 'mercurial.ini')]
530
530
531 def parse_patch_output(output_line):
531 def parse_patch_output(output_line):
532 """parses the output produced by patch and returns the file name"""
532 """parses the output produced by patch and returns the file name"""
533 pf = output_line[14:]
533 pf = output_line[14:]
534 if pf[0] == '`':
534 if pf[0] == '`':
535 pf = pf[1:-1] # Remove the quotes
535 pf = pf[1:-1] # Remove the quotes
536 return pf
536 return pf
537
537
538 try: # Mark Hammond's win32all package allows better functionality on Windows
538 try: # Mark Hammond's win32all package allows better functionality on Windows
539 import win32api, win32con, win32file, pywintypes
539 import win32api, win32con, win32file, winerror, pywintypes
540
540
541 # create hard links using win32file module
541 # create hard links using win32file module
542 def os_link(src, dst): # NB will only succeed on NTFS
542 def os_link(src, dst): # NB will only succeed on NTFS
543 win32file.CreateHardLink(dst, src)
543 win32file.CreateHardLink(dst, src)
544
544
545 def nlinks(pathname):
545 def nlinks(pathname):
546 """Return number of hardlinks for the given file."""
546 """Return number of hardlinks for the given file."""
547 try:
547 try:
548 fh = win32file.CreateFile(pathname,
548 fh = win32file.CreateFile(pathname,
549 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
549 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
550 None, win32file.OPEN_EXISTING, 0, None)
550 None, win32file.OPEN_EXISTING, 0, None)
551 res = win32file.GetFileInformationByHandle(fh)
551 res = win32file.GetFileInformationByHandle(fh)
552 fh.Close()
552 fh.Close()
553 return res[7]
553 return res[7]
554 except:
554 except:
555 return os.stat(pathname).st_nlink
555 return os.stat(pathname).st_nlink
556
556
557 def testpid(pid):
557 def testpid(pid):
558 '''return True if pid is still running or unable to determine, False otherwise'''
558 '''return True if pid is still running or unable to
559 determine, False otherwise'''
559 try:
560 try:
560 handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, pid)
561 handle = win32api.OpenProcess(
562 win32con.PROCESS_QUERY_INFORMATION, False, pid)
561 if handle:
563 if handle:
562 status = win32process.GetExitCodeProcess(handle)
564 status = win32process.GetExitCodeProcess(handle)
563 if status == win32con.STILL_ACTIVE:
565 return status == win32con.STILL_ACTIVE
564 return True
565 else:
566 return False
567 except pywintypes.error, details:
566 except pywintypes.error, details:
568 if details[0] == 87: # ERROR_INVALID_PARAMETER
567 return details[0] != winerror.ERROR_INVALID_PARAMETER
569 return False
570 return True
568 return True
571
569
572 except ImportError:
570 except ImportError:
573 def testpid(pid):
571 def testpid(pid):
574 '''return False if pid dead, True if running or not known'''
572 '''return False if pid dead, True if running or not known'''
575 return True
573 return True
576
574
577 def is_exec(f, last):
575 def is_exec(f, last):
578 return last
576 return last
579
577
580 def set_exec(f, mode):
578 def set_exec(f, mode):
581 pass
579 pass
582
580
583 def set_binary(fd):
581 def set_binary(fd):
584 msvcrt.setmode(fd.fileno(), os.O_BINARY)
582 msvcrt.setmode(fd.fileno(), os.O_BINARY)
585
583
586 def pconvert(path):
584 def pconvert(path):
587 return path.replace("\\", "/")
585 return path.replace("\\", "/")
588
586
589 def localpath(path):
587 def localpath(path):
590 return path.replace('/', '\\')
588 return path.replace('/', '\\')
591
589
592 def normpath(path):
590 def normpath(path):
593 return pconvert(os.path.normpath(path))
591 return pconvert(os.path.normpath(path))
594
592
595 makelock = _makelock_file
593 makelock = _makelock_file
596 readlock = _readlock_file
594 readlock = _readlock_file
597
595
598 def explain_exit(code):
596 def explain_exit(code):
599 return _("exited with status %d") % code, code
597 return _("exited with status %d") % code, code
600
598
601 else:
599 else:
602 nulldev = '/dev/null'
600 nulldev = '/dev/null'
603
601
604 def rcfiles(path):
602 def rcfiles(path):
605 rcs = [os.path.join(path, 'hgrc')]
603 rcs = [os.path.join(path, 'hgrc')]
606 rcdir = os.path.join(path, 'hgrc.d')
604 rcdir = os.path.join(path, 'hgrc.d')
607 try:
605 try:
608 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
606 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
609 if f.endswith(".rc")])
607 if f.endswith(".rc")])
610 except OSError, inst: pass
608 except OSError, inst: pass
611 return rcs
609 return rcs
612
610
613 def os_rcpath():
611 def os_rcpath():
614 '''return default os-specific hgrc search path'''
612 '''return default os-specific hgrc search path'''
615 path = []
613 path = []
616 if len(sys.argv) > 0:
614 if len(sys.argv) > 0:
617 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
615 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
618 '/../etc/mercurial'))
616 '/../etc/mercurial'))
619 path.extend(rcfiles('/etc/mercurial'))
617 path.extend(rcfiles('/etc/mercurial'))
620 path.append(os.path.expanduser('~/.hgrc'))
618 path.append(os.path.expanduser('~/.hgrc'))
621 path = [os.path.normpath(f) for f in path]
619 path = [os.path.normpath(f) for f in path]
622 return path
620 return path
623
621
624 def parse_patch_output(output_line):
622 def parse_patch_output(output_line):
625 """parses the output produced by patch and returns the file name"""
623 """parses the output produced by patch and returns the file name"""
626 pf = output_line[14:]
624 pf = output_line[14:]
627 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
625 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
628 pf = pf[1:-1] # Remove the quotes
626 pf = pf[1:-1] # Remove the quotes
629 return pf
627 return pf
630
628
631 def is_exec(f, last):
629 def is_exec(f, last):
632 """check whether a file is executable"""
630 """check whether a file is executable"""
633 return (os.stat(f).st_mode & 0100 != 0)
631 return (os.stat(f).st_mode & 0100 != 0)
634
632
635 def set_exec(f, mode):
633 def set_exec(f, mode):
636 s = os.stat(f).st_mode
634 s = os.stat(f).st_mode
637 if (s & 0100 != 0) == mode:
635 if (s & 0100 != 0) == mode:
638 return
636 return
639 if mode:
637 if mode:
640 # Turn on +x for every +r bit when making a file executable
638 # Turn on +x for every +r bit when making a file executable
641 # and obey umask.
639 # and obey umask.
642 umask = os.umask(0)
640 umask = os.umask(0)
643 os.umask(umask)
641 os.umask(umask)
644 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
642 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
645 else:
643 else:
646 os.chmod(f, s & 0666)
644 os.chmod(f, s & 0666)
647
645
648 def set_binary(fd):
646 def set_binary(fd):
649 pass
647 pass
650
648
651 def pconvert(path):
649 def pconvert(path):
652 return path
650 return path
653
651
654 def localpath(path):
652 def localpath(path):
655 return path
653 return path
656
654
657 normpath = os.path.normpath
655 normpath = os.path.normpath
658
656
659 def makelock(info, pathname):
657 def makelock(info, pathname):
660 try:
658 try:
661 os.symlink(info, pathname)
659 os.symlink(info, pathname)
662 except OSError, why:
660 except OSError, why:
663 if why.errno == errno.EEXIST:
661 if why.errno == errno.EEXIST:
664 raise
662 raise
665 else:
663 else:
666 _makelock_file(info, pathname)
664 _makelock_file(info, pathname)
667
665
668 def readlock(pathname):
666 def readlock(pathname):
669 try:
667 try:
670 return os.readlink(pathname)
668 return os.readlink(pathname)
671 except OSError, why:
669 except OSError, why:
672 if why.errno == errno.EINVAL:
670 if why.errno == errno.EINVAL:
673 return _readlock_file(pathname)
671 return _readlock_file(pathname)
674 else:
672 else:
675 raise
673 raise
676
674
677 def testpid(pid):
675 def testpid(pid):
678 '''return False if pid dead, True if running or not sure'''
676 '''return False if pid dead, True if running or not sure'''
679 try:
677 try:
680 os.kill(pid, 0)
678 os.kill(pid, 0)
681 return True
679 return True
682 except OSError, inst:
680 except OSError, inst:
683 return inst.errno != errno.ESRCH
681 return inst.errno != errno.ESRCH
684
682
685 def explain_exit(code):
683 def explain_exit(code):
686 """return a 2-tuple (desc, code) describing a process's status"""
684 """return a 2-tuple (desc, code) describing a process's status"""
687 if os.WIFEXITED(code):
685 if os.WIFEXITED(code):
688 val = os.WEXITSTATUS(code)
686 val = os.WEXITSTATUS(code)
689 return _("exited with status %d") % val, val
687 return _("exited with status %d") % val, val
690 elif os.WIFSIGNALED(code):
688 elif os.WIFSIGNALED(code):
691 val = os.WTERMSIG(code)
689 val = os.WTERMSIG(code)
692 return _("killed by signal %d") % val, val
690 return _("killed by signal %d") % val, val
693 elif os.WIFSTOPPED(code):
691 elif os.WIFSTOPPED(code):
694 val = os.WSTOPSIG(code)
692 val = os.WSTOPSIG(code)
695 return _("stopped by signal %d") % val, val
693 return _("stopped by signal %d") % val, val
696 raise ValueError(_("invalid exit code"))
694 raise ValueError(_("invalid exit code"))
697
695
698 class chunkbuffer(object):
696 class chunkbuffer(object):
699 """Allow arbitrary sized chunks of data to be efficiently read from an
697 """Allow arbitrary sized chunks of data to be efficiently read from an
700 iterator over chunks of arbitrary size."""
698 iterator over chunks of arbitrary size."""
701
699
702 def __init__(self, in_iter, targetsize = 2**16):
700 def __init__(self, in_iter, targetsize = 2**16):
703 """in_iter is the iterator that's iterating over the input chunks.
701 """in_iter is the iterator that's iterating over the input chunks.
704 targetsize is how big a buffer to try to maintain."""
702 targetsize is how big a buffer to try to maintain."""
705 self.in_iter = iter(in_iter)
703 self.in_iter = iter(in_iter)
706 self.buf = ''
704 self.buf = ''
707 self.targetsize = int(targetsize)
705 self.targetsize = int(targetsize)
708 if self.targetsize <= 0:
706 if self.targetsize <= 0:
709 raise ValueError(_("targetsize must be greater than 0, was %d") %
707 raise ValueError(_("targetsize must be greater than 0, was %d") %
710 targetsize)
708 targetsize)
711 self.iterempty = False
709 self.iterempty = False
712
710
713 def fillbuf(self):
711 def fillbuf(self):
714 """Ignore target size; read every chunk from iterator until empty."""
712 """Ignore target size; read every chunk from iterator until empty."""
715 if not self.iterempty:
713 if not self.iterempty:
716 collector = cStringIO.StringIO()
714 collector = cStringIO.StringIO()
717 collector.write(self.buf)
715 collector.write(self.buf)
718 for ch in self.in_iter:
716 for ch in self.in_iter:
719 collector.write(ch)
717 collector.write(ch)
720 self.buf = collector.getvalue()
718 self.buf = collector.getvalue()
721 self.iterempty = True
719 self.iterempty = True
722
720
723 def read(self, l):
721 def read(self, l):
724 """Read L bytes of data from the iterator of chunks of data.
722 """Read L bytes of data from the iterator of chunks of data.
725 Returns less than L bytes if the iterator runs dry."""
723 Returns less than L bytes if the iterator runs dry."""
726 if l > len(self.buf) and not self.iterempty:
724 if l > len(self.buf) and not self.iterempty:
727 # Clamp to a multiple of self.targetsize
725 # Clamp to a multiple of self.targetsize
728 targetsize = self.targetsize * ((l // self.targetsize) + 1)
726 targetsize = self.targetsize * ((l // self.targetsize) + 1)
729 collector = cStringIO.StringIO()
727 collector = cStringIO.StringIO()
730 collector.write(self.buf)
728 collector.write(self.buf)
731 collected = len(self.buf)
729 collected = len(self.buf)
732 for chunk in self.in_iter:
730 for chunk in self.in_iter:
733 collector.write(chunk)
731 collector.write(chunk)
734 collected += len(chunk)
732 collected += len(chunk)
735 if collected >= targetsize:
733 if collected >= targetsize:
736 break
734 break
737 if collected < targetsize:
735 if collected < targetsize:
738 self.iterempty = True
736 self.iterempty = True
739 self.buf = collector.getvalue()
737 self.buf = collector.getvalue()
740 s, self.buf = self.buf[:l], buffer(self.buf, l)
738 s, self.buf = self.buf[:l], buffer(self.buf, l)
741 return s
739 return s
742
740
743 def filechunkiter(f, size = 65536):
741 def filechunkiter(f, size = 65536):
744 """Create a generator that produces all the data in the file size
742 """Create a generator that produces all the data in the file size
745 (default 65536) bytes at a time. Chunks may be less than size
743 (default 65536) bytes at a time. Chunks may be less than size
746 bytes if the chunk is the last chunk in the file, or the file is a
744 bytes if the chunk is the last chunk in the file, or the file is a
747 socket or some other type of file that sometimes reads less data
745 socket or some other type of file that sometimes reads less data
748 than is requested."""
746 than is requested."""
749 s = f.read(size)
747 s = f.read(size)
750 while len(s) > 0:
748 while len(s) > 0:
751 yield s
749 yield s
752 s = f.read(size)
750 s = f.read(size)
753
751
754 def makedate():
752 def makedate():
755 lt = time.localtime()
753 lt = time.localtime()
756 if lt[8] == 1 and time.daylight:
754 if lt[8] == 1 and time.daylight:
757 tz = time.altzone
755 tz = time.altzone
758 else:
756 else:
759 tz = time.timezone
757 tz = time.timezone
760 return time.mktime(lt), tz
758 return time.mktime(lt), tz
761
759
762 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
760 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
763 """represent a (unixtime, offset) tuple as a localized time.
761 """represent a (unixtime, offset) tuple as a localized time.
764 unixtime is seconds since the epoch, and offset is the time zone's
762 unixtime is seconds since the epoch, and offset is the time zone's
765 number of seconds away from UTC. if timezone is false, do not
763 number of seconds away from UTC. if timezone is false, do not
766 append time zone to string."""
764 append time zone to string."""
767 t, tz = date or makedate()
765 t, tz = date or makedate()
768 s = time.strftime(format, time.gmtime(float(t) - tz))
766 s = time.strftime(format, time.gmtime(float(t) - tz))
769 if timezone:
767 if timezone:
770 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
768 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
771 return s
769 return s
772
770
773 def shortuser(user):
771 def shortuser(user):
774 """Return a short representation of a user name or email address."""
772 """Return a short representation of a user name or email address."""
775 f = user.find('@')
773 f = user.find('@')
776 if f >= 0:
774 if f >= 0:
777 user = user[:f]
775 user = user[:f]
778 f = user.find('<')
776 f = user.find('<')
779 if f >= 0:
777 if f >= 0:
780 user = user[f+1:]
778 user = user[f+1:]
781 return user
779 return user
782
780
783 def walkrepos(path):
781 def walkrepos(path):
784 '''yield every hg repository under path, recursively.'''
782 '''yield every hg repository under path, recursively.'''
785 def errhandler(err):
783 def errhandler(err):
786 if err.filename == path:
784 if err.filename == path:
787 raise err
785 raise err
788
786
789 for root, dirs, files in os.walk(path, onerror=errhandler):
787 for root, dirs, files in os.walk(path, onerror=errhandler):
790 for d in dirs:
788 for d in dirs:
791 if d == '.hg':
789 if d == '.hg':
792 yield root
790 yield root
793 dirs[:] = []
791 dirs[:] = []
794 break
792 break
795
793
796 _rcpath = None
794 _rcpath = None
797
795
798 def rcpath():
796 def rcpath():
799 '''return hgrc search path. if env var HGRCPATH is set, use it.
797 '''return hgrc search path. if env var HGRCPATH is set, use it.
800 for each item in path, if directory, use files ending in .rc,
798 for each item in path, if directory, use files ending in .rc,
801 else use item.
799 else use item.
802 make HGRCPATH empty to only look in .hg/hgrc of current repo.
800 make HGRCPATH empty to only look in .hg/hgrc of current repo.
803 if no HGRCPATH, use default os-specific path.'''
801 if no HGRCPATH, use default os-specific path.'''
804 global _rcpath
802 global _rcpath
805 if _rcpath is None:
803 if _rcpath is None:
806 if 'HGRCPATH' in os.environ:
804 if 'HGRCPATH' in os.environ:
807 _rcpath = []
805 _rcpath = []
808 for p in os.environ['HGRCPATH'].split(os.pathsep):
806 for p in os.environ['HGRCPATH'].split(os.pathsep):
809 if not p: continue
807 if not p: continue
810 if os.path.isdir(p):
808 if os.path.isdir(p):
811 for f in os.listdir(p):
809 for f in os.listdir(p):
812 if f.endswith('.rc'):
810 if f.endswith('.rc'):
813 _rcpath.append(os.path.join(p, f))
811 _rcpath.append(os.path.join(p, f))
814 else:
812 else:
815 _rcpath.append(p)
813 _rcpath.append(p)
816 else:
814 else:
817 _rcpath = os_rcpath()
815 _rcpath = os_rcpath()
818 return _rcpath
816 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now