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