##// END OF EJS Templates
more windows fixes.
Vadim Gelfer -
r2026:24c60462 default
parent child Browse files
Show More
@@ -1,816 +1,817 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(), "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, winerror, pywintypes
539 import win32api, win32con, win32file, 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
558 '''return True if pid is still running or unable to
559 determine, False otherwise'''
559 determine, False otherwise'''
560 try:
560 try:
561 import win32process, winerror
561 handle = win32api.OpenProcess(
562 handle = win32api.OpenProcess(
562 win32con.PROCESS_QUERY_INFORMATION, False, pid)
563 win32con.PROCESS_QUERY_INFORMATION, False, pid)
563 if handle:
564 if handle:
564 status = win32process.GetExitCodeProcess(handle)
565 status = win32process.GetExitCodeProcess(handle)
565 return status == win32con.STILL_ACTIVE
566 return status == win32con.STILL_ACTIVE
566 except pywintypes.error, details:
567 except pywintypes.error, details:
567 return details[0] != winerror.ERROR_INVALID_PARAMETER
568 return details[0] != winerror.ERROR_INVALID_PARAMETER
568 return True
569 return True
569
570
570 except ImportError:
571 except ImportError:
571 def testpid(pid):
572 def testpid(pid):
572 '''return False if pid dead, True if running or not known'''
573 '''return False if pid dead, True if running or not known'''
573 return True
574 return True
574
575
575 def is_exec(f, last):
576 def is_exec(f, last):
576 return last
577 return last
577
578
578 def set_exec(f, mode):
579 def set_exec(f, mode):
579 pass
580 pass
580
581
581 def set_binary(fd):
582 def set_binary(fd):
582 msvcrt.setmode(fd.fileno(), os.O_BINARY)
583 msvcrt.setmode(fd.fileno(), os.O_BINARY)
583
584
584 def pconvert(path):
585 def pconvert(path):
585 return path.replace("\\", "/")
586 return path.replace("\\", "/")
586
587
587 def localpath(path):
588 def localpath(path):
588 return path.replace('/', '\\')
589 return path.replace('/', '\\')
589
590
590 def normpath(path):
591 def normpath(path):
591 return pconvert(os.path.normpath(path))
592 return pconvert(os.path.normpath(path))
592
593
593 makelock = _makelock_file
594 makelock = _makelock_file
594 readlock = _readlock_file
595 readlock = _readlock_file
595
596
596 def explain_exit(code):
597 def explain_exit(code):
597 return _("exited with status %d") % code, code
598 return _("exited with status %d") % code, code
598
599
599 else:
600 else:
600 nulldev = '/dev/null'
601 nulldev = '/dev/null'
601
602
602 def rcfiles(path):
603 def rcfiles(path):
603 rcs = [os.path.join(path, 'hgrc')]
604 rcs = [os.path.join(path, 'hgrc')]
604 rcdir = os.path.join(path, 'hgrc.d')
605 rcdir = os.path.join(path, 'hgrc.d')
605 try:
606 try:
606 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
607 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
607 if f.endswith(".rc")])
608 if f.endswith(".rc")])
608 except OSError, inst: pass
609 except OSError, inst: pass
609 return rcs
610 return rcs
610
611
611 def os_rcpath():
612 def os_rcpath():
612 '''return default os-specific hgrc search path'''
613 '''return default os-specific hgrc search path'''
613 path = []
614 path = []
614 if len(sys.argv) > 0:
615 if len(sys.argv) > 0:
615 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
616 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
616 '/../etc/mercurial'))
617 '/../etc/mercurial'))
617 path.extend(rcfiles('/etc/mercurial'))
618 path.extend(rcfiles('/etc/mercurial'))
618 path.append(os.path.expanduser('~/.hgrc'))
619 path.append(os.path.expanduser('~/.hgrc'))
619 path = [os.path.normpath(f) for f in path]
620 path = [os.path.normpath(f) for f in path]
620 return path
621 return path
621
622
622 def parse_patch_output(output_line):
623 def parse_patch_output(output_line):
623 """parses the output produced by patch and returns the file name"""
624 """parses the output produced by patch and returns the file name"""
624 pf = output_line[14:]
625 pf = output_line[14:]
625 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
626 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
626 pf = pf[1:-1] # Remove the quotes
627 pf = pf[1:-1] # Remove the quotes
627 return pf
628 return pf
628
629
629 def is_exec(f, last):
630 def is_exec(f, last):
630 """check whether a file is executable"""
631 """check whether a file is executable"""
631 return (os.stat(f).st_mode & 0100 != 0)
632 return (os.stat(f).st_mode & 0100 != 0)
632
633
633 def set_exec(f, mode):
634 def set_exec(f, mode):
634 s = os.stat(f).st_mode
635 s = os.stat(f).st_mode
635 if (s & 0100 != 0) == mode:
636 if (s & 0100 != 0) == mode:
636 return
637 return
637 if mode:
638 if mode:
638 # Turn on +x for every +r bit when making a file executable
639 # Turn on +x for every +r bit when making a file executable
639 # and obey umask.
640 # and obey umask.
640 umask = os.umask(0)
641 umask = os.umask(0)
641 os.umask(umask)
642 os.umask(umask)
642 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
643 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
643 else:
644 else:
644 os.chmod(f, s & 0666)
645 os.chmod(f, s & 0666)
645
646
646 def set_binary(fd):
647 def set_binary(fd):
647 pass
648 pass
648
649
649 def pconvert(path):
650 def pconvert(path):
650 return path
651 return path
651
652
652 def localpath(path):
653 def localpath(path):
653 return path
654 return path
654
655
655 normpath = os.path.normpath
656 normpath = os.path.normpath
656
657
657 def makelock(info, pathname):
658 def makelock(info, pathname):
658 try:
659 try:
659 os.symlink(info, pathname)
660 os.symlink(info, pathname)
660 except OSError, why:
661 except OSError, why:
661 if why.errno == errno.EEXIST:
662 if why.errno == errno.EEXIST:
662 raise
663 raise
663 else:
664 else:
664 _makelock_file(info, pathname)
665 _makelock_file(info, pathname)
665
666
666 def readlock(pathname):
667 def readlock(pathname):
667 try:
668 try:
668 return os.readlink(pathname)
669 return os.readlink(pathname)
669 except OSError, why:
670 except OSError, why:
670 if why.errno == errno.EINVAL:
671 if why.errno == errno.EINVAL:
671 return _readlock_file(pathname)
672 return _readlock_file(pathname)
672 else:
673 else:
673 raise
674 raise
674
675
675 def testpid(pid):
676 def testpid(pid):
676 '''return False if pid dead, True if running or not sure'''
677 '''return False if pid dead, True if running or not sure'''
677 try:
678 try:
678 os.kill(pid, 0)
679 os.kill(pid, 0)
679 return True
680 return True
680 except OSError, inst:
681 except OSError, inst:
681 return inst.errno != errno.ESRCH
682 return inst.errno != errno.ESRCH
682
683
683 def explain_exit(code):
684 def explain_exit(code):
684 """return a 2-tuple (desc, code) describing a process's status"""
685 """return a 2-tuple (desc, code) describing a process's status"""
685 if os.WIFEXITED(code):
686 if os.WIFEXITED(code):
686 val = os.WEXITSTATUS(code)
687 val = os.WEXITSTATUS(code)
687 return _("exited with status %d") % val, val
688 return _("exited with status %d") % val, val
688 elif os.WIFSIGNALED(code):
689 elif os.WIFSIGNALED(code):
689 val = os.WTERMSIG(code)
690 val = os.WTERMSIG(code)
690 return _("killed by signal %d") % val, val
691 return _("killed by signal %d") % val, val
691 elif os.WIFSTOPPED(code):
692 elif os.WIFSTOPPED(code):
692 val = os.WSTOPSIG(code)
693 val = os.WSTOPSIG(code)
693 return _("stopped by signal %d") % val, val
694 return _("stopped by signal %d") % val, val
694 raise ValueError(_("invalid exit code"))
695 raise ValueError(_("invalid exit code"))
695
696
696 class chunkbuffer(object):
697 class chunkbuffer(object):
697 """Allow arbitrary sized chunks of data to be efficiently read from an
698 """Allow arbitrary sized chunks of data to be efficiently read from an
698 iterator over chunks of arbitrary size."""
699 iterator over chunks of arbitrary size."""
699
700
700 def __init__(self, in_iter, targetsize = 2**16):
701 def __init__(self, in_iter, targetsize = 2**16):
701 """in_iter is the iterator that's iterating over the input chunks.
702 """in_iter is the iterator that's iterating over the input chunks.
702 targetsize is how big a buffer to try to maintain."""
703 targetsize is how big a buffer to try to maintain."""
703 self.in_iter = iter(in_iter)
704 self.in_iter = iter(in_iter)
704 self.buf = ''
705 self.buf = ''
705 self.targetsize = int(targetsize)
706 self.targetsize = int(targetsize)
706 if self.targetsize <= 0:
707 if self.targetsize <= 0:
707 raise ValueError(_("targetsize must be greater than 0, was %d") %
708 raise ValueError(_("targetsize must be greater than 0, was %d") %
708 targetsize)
709 targetsize)
709 self.iterempty = False
710 self.iterempty = False
710
711
711 def fillbuf(self):
712 def fillbuf(self):
712 """Ignore target size; read every chunk from iterator until empty."""
713 """Ignore target size; read every chunk from iterator until empty."""
713 if not self.iterempty:
714 if not self.iterempty:
714 collector = cStringIO.StringIO()
715 collector = cStringIO.StringIO()
715 collector.write(self.buf)
716 collector.write(self.buf)
716 for ch in self.in_iter:
717 for ch in self.in_iter:
717 collector.write(ch)
718 collector.write(ch)
718 self.buf = collector.getvalue()
719 self.buf = collector.getvalue()
719 self.iterempty = True
720 self.iterempty = True
720
721
721 def read(self, l):
722 def read(self, l):
722 """Read L bytes of data from the iterator of chunks of data.
723 """Read L bytes of data from the iterator of chunks of data.
723 Returns less than L bytes if the iterator runs dry."""
724 Returns less than L bytes if the iterator runs dry."""
724 if l > len(self.buf) and not self.iterempty:
725 if l > len(self.buf) and not self.iterempty:
725 # Clamp to a multiple of self.targetsize
726 # Clamp to a multiple of self.targetsize
726 targetsize = self.targetsize * ((l // self.targetsize) + 1)
727 targetsize = self.targetsize * ((l // self.targetsize) + 1)
727 collector = cStringIO.StringIO()
728 collector = cStringIO.StringIO()
728 collector.write(self.buf)
729 collector.write(self.buf)
729 collected = len(self.buf)
730 collected = len(self.buf)
730 for chunk in self.in_iter:
731 for chunk in self.in_iter:
731 collector.write(chunk)
732 collector.write(chunk)
732 collected += len(chunk)
733 collected += len(chunk)
733 if collected >= targetsize:
734 if collected >= targetsize:
734 break
735 break
735 if collected < targetsize:
736 if collected < targetsize:
736 self.iterempty = True
737 self.iterempty = True
737 self.buf = collector.getvalue()
738 self.buf = collector.getvalue()
738 s, self.buf = self.buf[:l], buffer(self.buf, l)
739 s, self.buf = self.buf[:l], buffer(self.buf, l)
739 return s
740 return s
740
741
741 def filechunkiter(f, size = 65536):
742 def filechunkiter(f, size = 65536):
742 """Create a generator that produces all the data in the file size
743 """Create a generator that produces all the data in the file size
743 (default 65536) bytes at a time. Chunks may be less than size
744 (default 65536) bytes at a time. Chunks may be less than size
744 bytes if the chunk is the last chunk in the file, or the file is a
745 bytes if the chunk is the last chunk in the file, or the file is a
745 socket or some other type of file that sometimes reads less data
746 socket or some other type of file that sometimes reads less data
746 than is requested."""
747 than is requested."""
747 s = f.read(size)
748 s = f.read(size)
748 while len(s) > 0:
749 while len(s) > 0:
749 yield s
750 yield s
750 s = f.read(size)
751 s = f.read(size)
751
752
752 def makedate():
753 def makedate():
753 lt = time.localtime()
754 lt = time.localtime()
754 if lt[8] == 1 and time.daylight:
755 if lt[8] == 1 and time.daylight:
755 tz = time.altzone
756 tz = time.altzone
756 else:
757 else:
757 tz = time.timezone
758 tz = time.timezone
758 return time.mktime(lt), tz
759 return time.mktime(lt), tz
759
760
760 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
761 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
761 """represent a (unixtime, offset) tuple as a localized time.
762 """represent a (unixtime, offset) tuple as a localized time.
762 unixtime is seconds since the epoch, and offset is the time zone's
763 unixtime is seconds since the epoch, and offset is the time zone's
763 number of seconds away from UTC. if timezone is false, do not
764 number of seconds away from UTC. if timezone is false, do not
764 append time zone to string."""
765 append time zone to string."""
765 t, tz = date or makedate()
766 t, tz = date or makedate()
766 s = time.strftime(format, time.gmtime(float(t) - tz))
767 s = time.strftime(format, time.gmtime(float(t) - tz))
767 if timezone:
768 if timezone:
768 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
769 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
769 return s
770 return s
770
771
771 def shortuser(user):
772 def shortuser(user):
772 """Return a short representation of a user name or email address."""
773 """Return a short representation of a user name or email address."""
773 f = user.find('@')
774 f = user.find('@')
774 if f >= 0:
775 if f >= 0:
775 user = user[:f]
776 user = user[:f]
776 f = user.find('<')
777 f = user.find('<')
777 if f >= 0:
778 if f >= 0:
778 user = user[f+1:]
779 user = user[f+1:]
779 return user
780 return user
780
781
781 def walkrepos(path):
782 def walkrepos(path):
782 '''yield every hg repository under path, recursively.'''
783 '''yield every hg repository under path, recursively.'''
783 def errhandler(err):
784 def errhandler(err):
784 if err.filename == path:
785 if err.filename == path:
785 raise err
786 raise err
786
787
787 for root, dirs, files in os.walk(path, onerror=errhandler):
788 for root, dirs, files in os.walk(path, onerror=errhandler):
788 for d in dirs:
789 for d in dirs:
789 if d == '.hg':
790 if d == '.hg':
790 yield root
791 yield root
791 dirs[:] = []
792 dirs[:] = []
792 break
793 break
793
794
794 _rcpath = None
795 _rcpath = None
795
796
796 def rcpath():
797 def rcpath():
797 '''return hgrc search path. if env var HGRCPATH is set, use it.
798 '''return hgrc search path. if env var HGRCPATH is set, use it.
798 for each item in path, if directory, use files ending in .rc,
799 for each item in path, if directory, use files ending in .rc,
799 else use item.
800 else use item.
800 make HGRCPATH empty to only look in .hg/hgrc of current repo.
801 make HGRCPATH empty to only look in .hg/hgrc of current repo.
801 if no HGRCPATH, use default os-specific path.'''
802 if no HGRCPATH, use default os-specific path.'''
802 global _rcpath
803 global _rcpath
803 if _rcpath is None:
804 if _rcpath is None:
804 if 'HGRCPATH' in os.environ:
805 if 'HGRCPATH' in os.environ:
805 _rcpath = []
806 _rcpath = []
806 for p in os.environ['HGRCPATH'].split(os.pathsep):
807 for p in os.environ['HGRCPATH'].split(os.pathsep):
807 if not p: continue
808 if not p: continue
808 if os.path.isdir(p):
809 if os.path.isdir(p):
809 for f in os.listdir(p):
810 for f in os.listdir(p):
810 if f.endswith('.rc'):
811 if f.endswith('.rc'):
811 _rcpath.append(os.path.join(p, f))
812 _rcpath.append(os.path.join(p, f))
812 else:
813 else:
813 _rcpath.append(p)
814 _rcpath.append(p)
814 else:
815 else:
815 _rcpath = os_rcpath()
816 _rcpath = os_rcpath()
816 return _rcpath
817 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now