##// END OF EJS Templates
dirstate.statwalk: explicitly test for ignored directories...
Alexis S. L. Carvalho -
r4193:dd0d9bd9 default
parent child Browse files
Show More
@@ -1,531 +1,533 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.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
9
10 from node import *
10 from node import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 from demandload import *
12 from demandload import *
13 demandload(globals(), "struct os time bisect stat strutil util re errno")
13 demandload(globals(), "struct os time bisect stat strutil util re errno")
14
14
15 class dirstate(object):
15 class dirstate(object):
16 format = ">cllll"
16 format = ">cllll"
17
17
18 def __init__(self, opener, ui, root):
18 def __init__(self, opener, ui, root):
19 self.opener = opener
19 self.opener = opener
20 self.root = root
20 self.root = root
21 self.dirty = 0
21 self.dirty = 0
22 self.ui = ui
22 self.ui = ui
23 self.map = None
23 self.map = None
24 self.pl = None
24 self.pl = None
25 self.dirs = None
25 self.dirs = None
26 self.copymap = {}
26 self.copymap = {}
27 self.ignorefunc = None
27 self.ignorefunc = None
28
28
29 def wjoin(self, f):
29 def wjoin(self, f):
30 return os.path.join(self.root, f)
30 return os.path.join(self.root, f)
31
31
32 def getcwd(self):
32 def getcwd(self):
33 cwd = os.getcwd()
33 cwd = os.getcwd()
34 if cwd == self.root: return ''
34 if cwd == self.root: return ''
35 # self.root ends with a path separator if self.root is '/' or 'C:\'
35 # self.root ends with a path separator if self.root is '/' or 'C:\'
36 common_prefix_len = len(self.root)
36 common_prefix_len = len(self.root)
37 if not self.root.endswith(os.sep):
37 if not self.root.endswith(os.sep):
38 common_prefix_len += 1
38 common_prefix_len += 1
39 return cwd[common_prefix_len:]
39 return cwd[common_prefix_len:]
40
40
41 def hgignore(self):
41 def hgignore(self):
42 '''return the contents of .hgignore files as a list of patterns.
42 '''return the contents of .hgignore files as a list of patterns.
43
43
44 the files parsed for patterns include:
44 the files parsed for patterns include:
45 .hgignore in the repository root
45 .hgignore in the repository root
46 any additional files specified in the [ui] section of ~/.hgrc
46 any additional files specified in the [ui] section of ~/.hgrc
47
47
48 trailing white space is dropped.
48 trailing white space is dropped.
49 the escape character is backslash.
49 the escape character is backslash.
50 comments start with #.
50 comments start with #.
51 empty lines are skipped.
51 empty lines are skipped.
52
52
53 lines can be of the following formats:
53 lines can be of the following formats:
54
54
55 syntax: regexp # defaults following lines to non-rooted regexps
55 syntax: regexp # defaults following lines to non-rooted regexps
56 syntax: glob # defaults following lines to non-rooted globs
56 syntax: glob # defaults following lines to non-rooted globs
57 re:pattern # non-rooted regular expression
57 re:pattern # non-rooted regular expression
58 glob:pattern # non-rooted glob
58 glob:pattern # non-rooted glob
59 pattern # pattern of the current default type'''
59 pattern # pattern of the current default type'''
60 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
60 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
61 def parselines(fp):
61 def parselines(fp):
62 for line in fp:
62 for line in fp:
63 escape = False
63 escape = False
64 for i in xrange(len(line)):
64 for i in xrange(len(line)):
65 if escape: escape = False
65 if escape: escape = False
66 elif line[i] == '\\': escape = True
66 elif line[i] == '\\': escape = True
67 elif line[i] == '#': break
67 elif line[i] == '#': break
68 line = line[:i].rstrip()
68 line = line[:i].rstrip()
69 if line: yield line
69 if line: yield line
70 repoignore = self.wjoin('.hgignore')
70 repoignore = self.wjoin('.hgignore')
71 files = [repoignore]
71 files = [repoignore]
72 files.extend(self.ui.hgignorefiles())
72 files.extend(self.ui.hgignorefiles())
73 pats = {}
73 pats = {}
74 for f in files:
74 for f in files:
75 try:
75 try:
76 pats[f] = []
76 pats[f] = []
77 fp = open(f)
77 fp = open(f)
78 syntax = 'relre:'
78 syntax = 'relre:'
79 for line in parselines(fp):
79 for line in parselines(fp):
80 if line.startswith('syntax:'):
80 if line.startswith('syntax:'):
81 s = line[7:].strip()
81 s = line[7:].strip()
82 try:
82 try:
83 syntax = syntaxes[s]
83 syntax = syntaxes[s]
84 except KeyError:
84 except KeyError:
85 self.ui.warn(_("%s: ignoring invalid "
85 self.ui.warn(_("%s: ignoring invalid "
86 "syntax '%s'\n") % (f, s))
86 "syntax '%s'\n") % (f, s))
87 continue
87 continue
88 pat = syntax + line
88 pat = syntax + line
89 for s in syntaxes.values():
89 for s in syntaxes.values():
90 if line.startswith(s):
90 if line.startswith(s):
91 pat = line
91 pat = line
92 break
92 break
93 pats[f].append(pat)
93 pats[f].append(pat)
94 except IOError, inst:
94 except IOError, inst:
95 if f != repoignore:
95 if f != repoignore:
96 self.ui.warn(_("skipping unreadable ignore file"
96 self.ui.warn(_("skipping unreadable ignore file"
97 " '%s': %s\n") % (f, inst.strerror))
97 " '%s': %s\n") % (f, inst.strerror))
98 return pats
98 return pats
99
99
100 def ignore(self, fn):
100 def ignore(self, fn):
101 '''default match function used by dirstate and
101 '''default match function used by dirstate and
102 localrepository. this honours the repository .hgignore file
102 localrepository. this honours the repository .hgignore file
103 and any other files specified in the [ui] section of .hgrc.'''
103 and any other files specified in the [ui] section of .hgrc.'''
104 if not self.ignorefunc:
104 if not self.ignorefunc:
105 ignore = self.hgignore()
105 ignore = self.hgignore()
106 allpats = []
106 allpats = []
107 [allpats.extend(patlist) for patlist in ignore.values()]
107 [allpats.extend(patlist) for patlist in ignore.values()]
108 if allpats:
108 if allpats:
109 try:
109 try:
110 files, self.ignorefunc, anypats = (
110 files, self.ignorefunc, anypats = (
111 util.matcher(self.root, inc=allpats, src='.hgignore'))
111 util.matcher(self.root, inc=allpats, src='.hgignore'))
112 except util.Abort:
112 except util.Abort:
113 # Re-raise an exception where the src is the right file
113 # Re-raise an exception where the src is the right file
114 for f, patlist in ignore.items():
114 for f, patlist in ignore.items():
115 files, self.ignorefunc, anypats = (
115 files, self.ignorefunc, anypats = (
116 util.matcher(self.root, inc=patlist, src=f))
116 util.matcher(self.root, inc=patlist, src=f))
117 else:
117 else:
118 self.ignorefunc = util.never
118 self.ignorefunc = util.never
119 return self.ignorefunc(fn)
119 return self.ignorefunc(fn)
120
120
121 def __del__(self):
121 def __del__(self):
122 if self.dirty:
122 if self.dirty:
123 self.write()
123 self.write()
124
124
125 def __getitem__(self, key):
125 def __getitem__(self, key):
126 try:
126 try:
127 return self.map[key]
127 return self.map[key]
128 except TypeError:
128 except TypeError:
129 self.lazyread()
129 self.lazyread()
130 return self[key]
130 return self[key]
131
131
132 def __contains__(self, key):
132 def __contains__(self, key):
133 self.lazyread()
133 self.lazyread()
134 return key in self.map
134 return key in self.map
135
135
136 def parents(self):
136 def parents(self):
137 self.lazyread()
137 self.lazyread()
138 return self.pl
138 return self.pl
139
139
140 def markdirty(self):
140 def markdirty(self):
141 if not self.dirty:
141 if not self.dirty:
142 self.dirty = 1
142 self.dirty = 1
143
143
144 def setparents(self, p1, p2=nullid):
144 def setparents(self, p1, p2=nullid):
145 self.lazyread()
145 self.lazyread()
146 self.markdirty()
146 self.markdirty()
147 self.pl = p1, p2
147 self.pl = p1, p2
148
148
149 def state(self, key):
149 def state(self, key):
150 try:
150 try:
151 return self[key][0]
151 return self[key][0]
152 except KeyError:
152 except KeyError:
153 return "?"
153 return "?"
154
154
155 def lazyread(self):
155 def lazyread(self):
156 if self.map is None:
156 if self.map is None:
157 self.read()
157 self.read()
158
158
159 def parse(self, st):
159 def parse(self, st):
160 self.pl = [st[:20], st[20: 40]]
160 self.pl = [st[:20], st[20: 40]]
161
161
162 # deref fields so they will be local in loop
162 # deref fields so they will be local in loop
163 map = self.map
163 map = self.map
164 copymap = self.copymap
164 copymap = self.copymap
165 format = self.format
165 format = self.format
166 unpack = struct.unpack
166 unpack = struct.unpack
167
167
168 pos = 40
168 pos = 40
169 e_size = struct.calcsize(format)
169 e_size = struct.calcsize(format)
170
170
171 while pos < len(st):
171 while pos < len(st):
172 newpos = pos + e_size
172 newpos = pos + e_size
173 e = unpack(format, st[pos:newpos])
173 e = unpack(format, st[pos:newpos])
174 l = e[4]
174 l = e[4]
175 pos = newpos
175 pos = newpos
176 newpos = pos + l
176 newpos = pos + l
177 f = st[pos:newpos]
177 f = st[pos:newpos]
178 if '\0' in f:
178 if '\0' in f:
179 f, c = f.split('\0')
179 f, c = f.split('\0')
180 copymap[f] = c
180 copymap[f] = c
181 map[f] = e[:4]
181 map[f] = e[:4]
182 pos = newpos
182 pos = newpos
183
183
184 def read(self):
184 def read(self):
185 self.map = {}
185 self.map = {}
186 self.pl = [nullid, nullid]
186 self.pl = [nullid, nullid]
187 try:
187 try:
188 st = self.opener("dirstate").read()
188 st = self.opener("dirstate").read()
189 if st:
189 if st:
190 self.parse(st)
190 self.parse(st)
191 except IOError, err:
191 except IOError, err:
192 if err.errno != errno.ENOENT: raise
192 if err.errno != errno.ENOENT: raise
193
193
194 def copy(self, source, dest):
194 def copy(self, source, dest):
195 self.lazyread()
195 self.lazyread()
196 self.markdirty()
196 self.markdirty()
197 self.copymap[dest] = source
197 self.copymap[dest] = source
198
198
199 def copied(self, file):
199 def copied(self, file):
200 return self.copymap.get(file, None)
200 return self.copymap.get(file, None)
201
201
202 def copies(self):
202 def copies(self):
203 return self.copymap
203 return self.copymap
204
204
205 def initdirs(self):
205 def initdirs(self):
206 if self.dirs is None:
206 if self.dirs is None:
207 self.dirs = {}
207 self.dirs = {}
208 for f in self.map:
208 for f in self.map:
209 self.updatedirs(f, 1)
209 self.updatedirs(f, 1)
210
210
211 def updatedirs(self, path, delta):
211 def updatedirs(self, path, delta):
212 if self.dirs is not None:
212 if self.dirs is not None:
213 for c in strutil.findall(path, '/'):
213 for c in strutil.findall(path, '/'):
214 pc = path[:c]
214 pc = path[:c]
215 self.dirs.setdefault(pc, 0)
215 self.dirs.setdefault(pc, 0)
216 self.dirs[pc] += delta
216 self.dirs[pc] += delta
217
217
218 def checkinterfering(self, files):
218 def checkinterfering(self, files):
219 def prefixes(f):
219 def prefixes(f):
220 for c in strutil.rfindall(f, '/'):
220 for c in strutil.rfindall(f, '/'):
221 yield f[:c]
221 yield f[:c]
222 self.lazyread()
222 self.lazyread()
223 self.initdirs()
223 self.initdirs()
224 seendirs = {}
224 seendirs = {}
225 for f in files:
225 for f in files:
226 # shadows
226 # shadows
227 if self.dirs.get(f):
227 if self.dirs.get(f):
228 raise util.Abort(_('directory named %r already in dirstate') %
228 raise util.Abort(_('directory named %r already in dirstate') %
229 f)
229 f)
230 for d in prefixes(f):
230 for d in prefixes(f):
231 if d in seendirs:
231 if d in seendirs:
232 break
232 break
233 if d in self.map:
233 if d in self.map:
234 raise util.Abort(_('file named %r already in dirstate') %
234 raise util.Abort(_('file named %r already in dirstate') %
235 d)
235 d)
236 seendirs[d] = True
236 seendirs[d] = True
237 # disallowed
237 # disallowed
238 if '\r' in f or '\n' in f:
238 if '\r' in f or '\n' in f:
239 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
239 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
240
240
241 def update(self, files, state, **kw):
241 def update(self, files, state, **kw):
242 ''' current states:
242 ''' current states:
243 n normal
243 n normal
244 m needs merging
244 m needs merging
245 r marked for removal
245 r marked for removal
246 a marked for addition'''
246 a marked for addition'''
247
247
248 if not files: return
248 if not files: return
249 self.lazyread()
249 self.lazyread()
250 self.markdirty()
250 self.markdirty()
251 if state == "a":
251 if state == "a":
252 self.initdirs()
252 self.initdirs()
253 self.checkinterfering(files)
253 self.checkinterfering(files)
254 for f in files:
254 for f in files:
255 if state == "r":
255 if state == "r":
256 self.map[f] = ('r', 0, 0, 0)
256 self.map[f] = ('r', 0, 0, 0)
257 self.updatedirs(f, -1)
257 self.updatedirs(f, -1)
258 else:
258 else:
259 if state == "a":
259 if state == "a":
260 self.updatedirs(f, 1)
260 self.updatedirs(f, 1)
261 s = os.lstat(self.wjoin(f))
261 s = os.lstat(self.wjoin(f))
262 st_size = kw.get('st_size', s.st_size)
262 st_size = kw.get('st_size', s.st_size)
263 st_mtime = kw.get('st_mtime', s.st_mtime)
263 st_mtime = kw.get('st_mtime', s.st_mtime)
264 self.map[f] = (state, s.st_mode, st_size, st_mtime)
264 self.map[f] = (state, s.st_mode, st_size, st_mtime)
265 if self.copymap.has_key(f):
265 if self.copymap.has_key(f):
266 del self.copymap[f]
266 del self.copymap[f]
267
267
268 def forget(self, files):
268 def forget(self, files):
269 if not files: return
269 if not files: return
270 self.lazyread()
270 self.lazyread()
271 self.markdirty()
271 self.markdirty()
272 self.initdirs()
272 self.initdirs()
273 for f in files:
273 for f in files:
274 try:
274 try:
275 del self.map[f]
275 del self.map[f]
276 self.updatedirs(f, -1)
276 self.updatedirs(f, -1)
277 except KeyError:
277 except KeyError:
278 self.ui.warn(_("not in dirstate: %s!\n") % f)
278 self.ui.warn(_("not in dirstate: %s!\n") % f)
279 pass
279 pass
280
280
281 def clear(self):
281 def clear(self):
282 self.map = {}
282 self.map = {}
283 self.copymap = {}
283 self.copymap = {}
284 self.dirs = None
284 self.dirs = None
285 self.markdirty()
285 self.markdirty()
286
286
287 def rebuild(self, parent, files):
287 def rebuild(self, parent, files):
288 self.clear()
288 self.clear()
289 for f in files:
289 for f in files:
290 if files.execf(f):
290 if files.execf(f):
291 self.map[f] = ('n', 0777, -1, 0)
291 self.map[f] = ('n', 0777, -1, 0)
292 else:
292 else:
293 self.map[f] = ('n', 0666, -1, 0)
293 self.map[f] = ('n', 0666, -1, 0)
294 self.pl = (parent, nullid)
294 self.pl = (parent, nullid)
295 self.markdirty()
295 self.markdirty()
296
296
297 def write(self):
297 def write(self):
298 if not self.dirty:
298 if not self.dirty:
299 return
299 return
300 st = self.opener("dirstate", "w", atomic=True)
300 st = self.opener("dirstate", "w", atomic=True)
301 st.write("".join(self.pl))
301 st.write("".join(self.pl))
302 for f, e in self.map.items():
302 for f, e in self.map.items():
303 c = self.copied(f)
303 c = self.copied(f)
304 if c:
304 if c:
305 f = f + "\0" + c
305 f = f + "\0" + c
306 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
306 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
307 st.write(e + f)
307 st.write(e + f)
308 self.dirty = 0
308 self.dirty = 0
309
309
310 def filterfiles(self, files):
310 def filterfiles(self, files):
311 ret = {}
311 ret = {}
312 unknown = []
312 unknown = []
313
313
314 for x in files:
314 for x in files:
315 if x == '.':
315 if x == '.':
316 return self.map.copy()
316 return self.map.copy()
317 if x not in self.map:
317 if x not in self.map:
318 unknown.append(x)
318 unknown.append(x)
319 else:
319 else:
320 ret[x] = self.map[x]
320 ret[x] = self.map[x]
321
321
322 if not unknown:
322 if not unknown:
323 return ret
323 return ret
324
324
325 b = self.map.keys()
325 b = self.map.keys()
326 b.sort()
326 b.sort()
327 blen = len(b)
327 blen = len(b)
328
328
329 for x in unknown:
329 for x in unknown:
330 bs = bisect.bisect(b, "%s%s" % (x, '/'))
330 bs = bisect.bisect(b, "%s%s" % (x, '/'))
331 while bs < blen:
331 while bs < blen:
332 s = b[bs]
332 s = b[bs]
333 if len(s) > len(x) and s.startswith(x):
333 if len(s) > len(x) and s.startswith(x):
334 ret[s] = self.map[s]
334 ret[s] = self.map[s]
335 else:
335 else:
336 break
336 break
337 bs += 1
337 bs += 1
338 return ret
338 return ret
339
339
340 def supported_type(self, f, st, verbose=False):
340 def supported_type(self, f, st, verbose=False):
341 if stat.S_ISREG(st.st_mode):
341 if stat.S_ISREG(st.st_mode):
342 return True
342 return True
343 if verbose:
343 if verbose:
344 kind = 'unknown'
344 kind = 'unknown'
345 if stat.S_ISCHR(st.st_mode): kind = _('character device')
345 if stat.S_ISCHR(st.st_mode): kind = _('character device')
346 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
346 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
347 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
347 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
348 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
348 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
349 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
349 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
350 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
350 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
351 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
351 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
352 util.pathto(self.getcwd(), f),
352 util.pathto(self.getcwd(), f),
353 kind))
353 kind))
354 return False
354 return False
355
355
356 def walk(self, files=None, match=util.always, badmatch=None):
356 def walk(self, files=None, match=util.always, badmatch=None):
357 # filter out the stat
357 # filter out the stat
358 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
358 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
359 yield src, f
359 yield src, f
360
360
361 def statwalk(self, files=None, match=util.always, ignored=False,
361 def statwalk(self, files=None, match=util.always, ignored=False,
362 badmatch=None):
362 badmatch=None):
363 '''
363 '''
364 walk recursively through the directory tree, finding all files
364 walk recursively through the directory tree, finding all files
365 matched by the match function
365 matched by the match function
366
366
367 results are yielded in a tuple (src, filename, st), where src
367 results are yielded in a tuple (src, filename, st), where src
368 is one of:
368 is one of:
369 'f' the file was found in the directory tree
369 'f' the file was found in the directory tree
370 'm' the file was only in the dirstate and not in the tree
370 'm' the file was only in the dirstate and not in the tree
371 'b' file was not found and matched badmatch
371 'b' file was not found and matched badmatch
372
372
373 and st is the stat result if the file was found in the directory.
373 and st is the stat result if the file was found in the directory.
374 '''
374 '''
375 self.lazyread()
375 self.lazyread()
376
376
377 # walk all files by default
377 # walk all files by default
378 if not files:
378 if not files:
379 files = ['.']
379 files = ['.']
380 dc = self.map.copy()
380 dc = self.map.copy()
381 else:
381 else:
382 files = util.unique(files)
382 files = util.unique(files)
383 dc = self.filterfiles(files)
383 dc = self.filterfiles(files)
384
384
385 def imatch(file_):
385 def imatch(file_):
386 if file_ not in dc and self.ignore(file_):
386 if file_ not in dc and self.ignore(file_):
387 return False
387 return False
388 return match(file_)
388 return match(file_)
389
389
390 if ignored: imatch = match
390 ignore = self.ignore
391 if ignored:
392 imatch = match
393 ignore = util.never
391
394
392 # self.root may end with a path separator when self.root == '/'
395 # self.root may end with a path separator when self.root == '/'
393 common_prefix_len = len(self.root)
396 common_prefix_len = len(self.root)
394 if not self.root.endswith(os.sep):
397 if not self.root.endswith(os.sep):
395 common_prefix_len += 1
398 common_prefix_len += 1
396 # recursion free walker, faster than os.walk.
399 # recursion free walker, faster than os.walk.
397 def findfiles(s):
400 def findfiles(s):
398 work = [s]
401 work = [s]
399 while work:
402 while work:
400 top = work.pop()
403 top = work.pop()
401 names = os.listdir(top)
404 names = os.listdir(top)
402 names.sort()
405 names.sort()
403 # nd is the top of the repository dir tree
406 # nd is the top of the repository dir tree
404 nd = util.normpath(top[common_prefix_len:])
407 nd = util.normpath(top[common_prefix_len:])
405 if nd == '.':
408 if nd == '.':
406 nd = ''
409 nd = ''
407 else:
410 else:
408 # do not recurse into a repo contained in this
411 # do not recurse into a repo contained in this
409 # one. use bisect to find .hg directory so speed
412 # one. use bisect to find .hg directory so speed
410 # is good on big directory.
413 # is good on big directory.
411 hg = bisect.bisect_left(names, '.hg')
414 hg = bisect.bisect_left(names, '.hg')
412 if hg < len(names) and names[hg] == '.hg':
415 if hg < len(names) and names[hg] == '.hg':
413 if os.path.isdir(os.path.join(top, '.hg')):
416 if os.path.isdir(os.path.join(top, '.hg')):
414 continue
417 continue
415 for f in names:
418 for f in names:
416 np = util.pconvert(os.path.join(nd, f))
419 np = util.pconvert(os.path.join(nd, f))
417 if seen(np):
420 if seen(np):
418 continue
421 continue
419 p = os.path.join(top, f)
422 p = os.path.join(top, f)
420 # don't trip over symlinks
423 # don't trip over symlinks
421 st = os.lstat(p)
424 st = os.lstat(p)
422 if stat.S_ISDIR(st.st_mode):
425 if stat.S_ISDIR(st.st_mode):
423 ds = util.pconvert(os.path.join(nd, f +'/'))
426 if not ignore(p):
424 if imatch(ds):
425 work.append(p)
427 work.append(p)
426 if imatch(np) and np in dc:
428 if imatch(np) and np in dc:
427 yield 'm', np, st
429 yield 'm', np, st
428 elif imatch(np):
430 elif imatch(np):
429 if self.supported_type(np, st):
431 if self.supported_type(np, st):
430 yield 'f', np, st
432 yield 'f', np, st
431 elif np in dc:
433 elif np in dc:
432 yield 'm', np, st
434 yield 'm', np, st
433
435
434 known = {'.hg': 1}
436 known = {'.hg': 1}
435 def seen(fn):
437 def seen(fn):
436 if fn in known: return True
438 if fn in known: return True
437 known[fn] = 1
439 known[fn] = 1
438
440
439 # step one, find all files that match our criteria
441 # step one, find all files that match our criteria
440 files.sort()
442 files.sort()
441 for ff in files:
443 for ff in files:
442 nf = util.normpath(ff)
444 nf = util.normpath(ff)
443 f = self.wjoin(ff)
445 f = self.wjoin(ff)
444 try:
446 try:
445 st = os.lstat(f)
447 st = os.lstat(f)
446 except OSError, inst:
448 except OSError, inst:
447 found = False
449 found = False
448 for fn in dc:
450 for fn in dc:
449 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
451 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
450 found = True
452 found = True
451 break
453 break
452 if not found:
454 if not found:
453 if inst.errno != errno.ENOENT or not badmatch:
455 if inst.errno != errno.ENOENT or not badmatch:
454 self.ui.warn('%s: %s\n' % (
456 self.ui.warn('%s: %s\n' % (
455 util.pathto(self.getcwd(), ff),
457 util.pathto(self.getcwd(), ff),
456 inst.strerror))
458 inst.strerror))
457 elif badmatch and badmatch(ff) and imatch(nf):
459 elif badmatch and badmatch(ff) and imatch(nf):
458 yield 'b', ff, None
460 yield 'b', ff, None
459 continue
461 continue
460 if stat.S_ISDIR(st.st_mode):
462 if stat.S_ISDIR(st.st_mode):
461 cmp1 = (lambda x, y: cmp(x[1], y[1]))
463 cmp1 = (lambda x, y: cmp(x[1], y[1]))
462 sorted_ = [ x for x in findfiles(f) ]
464 sorted_ = [ x for x in findfiles(f) ]
463 sorted_.sort(cmp1)
465 sorted_.sort(cmp1)
464 for e in sorted_:
466 for e in sorted_:
465 yield e
467 yield e
466 else:
468 else:
467 if not seen(nf) and match(nf):
469 if not seen(nf) and match(nf):
468 if self.supported_type(ff, st, verbose=True):
470 if self.supported_type(ff, st, verbose=True):
469 yield 'f', nf, st
471 yield 'f', nf, st
470 elif ff in dc:
472 elif ff in dc:
471 yield 'm', nf, st
473 yield 'm', nf, st
472
474
473 # step two run through anything left in the dc hash and yield
475 # step two run through anything left in the dc hash and yield
474 # if we haven't already seen it
476 # if we haven't already seen it
475 ks = dc.keys()
477 ks = dc.keys()
476 ks.sort()
478 ks.sort()
477 for k in ks:
479 for k in ks:
478 if not seen(k) and imatch(k):
480 if not seen(k) and imatch(k):
479 yield 'm', k, None
481 yield 'm', k, None
480
482
481 def status(self, files=None, match=util.always, list_ignored=False,
483 def status(self, files=None, match=util.always, list_ignored=False,
482 list_clean=False):
484 list_clean=False):
483 lookup, modified, added, unknown, ignored = [], [], [], [], []
485 lookup, modified, added, unknown, ignored = [], [], [], [], []
484 removed, deleted, clean = [], [], []
486 removed, deleted, clean = [], [], []
485
487
486 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
488 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
487 try:
489 try:
488 type_, mode, size, time = self[fn]
490 type_, mode, size, time = self[fn]
489 except KeyError:
491 except KeyError:
490 if list_ignored and self.ignore(fn):
492 if list_ignored and self.ignore(fn):
491 ignored.append(fn)
493 ignored.append(fn)
492 else:
494 else:
493 unknown.append(fn)
495 unknown.append(fn)
494 continue
496 continue
495 if src == 'm':
497 if src == 'm':
496 nonexistent = True
498 nonexistent = True
497 if not st:
499 if not st:
498 try:
500 try:
499 st = os.lstat(self.wjoin(fn))
501 st = os.lstat(self.wjoin(fn))
500 except OSError, inst:
502 except OSError, inst:
501 if inst.errno != errno.ENOENT:
503 if inst.errno != errno.ENOENT:
502 raise
504 raise
503 st = None
505 st = None
504 # We need to re-check that it is a valid file
506 # We need to re-check that it is a valid file
505 if st and self.supported_type(fn, st):
507 if st and self.supported_type(fn, st):
506 nonexistent = False
508 nonexistent = False
507 # XXX: what to do with file no longer present in the fs
509 # XXX: what to do with file no longer present in the fs
508 # who are not removed in the dirstate ?
510 # who are not removed in the dirstate ?
509 if nonexistent and type_ in "nm":
511 if nonexistent and type_ in "nm":
510 deleted.append(fn)
512 deleted.append(fn)
511 continue
513 continue
512 # check the common case first
514 # check the common case first
513 if type_ == 'n':
515 if type_ == 'n':
514 if not st:
516 if not st:
515 st = os.lstat(self.wjoin(fn))
517 st = os.lstat(self.wjoin(fn))
516 if size >= 0 and (size != st.st_size
518 if size >= 0 and (size != st.st_size
517 or (mode ^ st.st_mode) & 0100):
519 or (mode ^ st.st_mode) & 0100):
518 modified.append(fn)
520 modified.append(fn)
519 elif time != int(st.st_mtime):
521 elif time != int(st.st_mtime):
520 lookup.append(fn)
522 lookup.append(fn)
521 elif list_clean:
523 elif list_clean:
522 clean.append(fn)
524 clean.append(fn)
523 elif type_ == 'm':
525 elif type_ == 'm':
524 modified.append(fn)
526 modified.append(fn)
525 elif type_ == 'a':
527 elif type_ == 'a':
526 added.append(fn)
528 added.append(fn)
527 elif type_ == 'r':
529 elif type_ == 'r':
528 removed.append(fn)
530 removed.append(fn)
529
531
530 return (lookup, modified, added, removed, deleted, unknown, ignored,
532 return (lookup, modified, added, removed, deleted, unknown, ignored,
531 clean)
533 clean)
@@ -1,1379 +1,1378 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 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import gettext as _
15 from i18n import gettext as _
16 from demandload import *
16 from demandload import *
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
19
19
20 try:
20 try:
21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
22 or "ascii"
22 or "ascii"
23 except locale.Error:
23 except locale.Error:
24 _encoding = 'ascii'
24 _encoding = 'ascii'
25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
26 _fallbackencoding = 'ISO-8859-1'
26 _fallbackencoding = 'ISO-8859-1'
27
27
28 def tolocal(s):
28 def tolocal(s):
29 """
29 """
30 Convert a string from internal UTF-8 to local encoding
30 Convert a string from internal UTF-8 to local encoding
31
31
32 All internal strings should be UTF-8 but some repos before the
32 All internal strings should be UTF-8 but some repos before the
33 implementation of locale support may contain latin1 or possibly
33 implementation of locale support may contain latin1 or possibly
34 other character sets. We attempt to decode everything strictly
34 other character sets. We attempt to decode everything strictly
35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
36 replace unknown characters.
36 replace unknown characters.
37 """
37 """
38 for e in ('UTF-8', _fallbackencoding):
38 for e in ('UTF-8', _fallbackencoding):
39 try:
39 try:
40 u = s.decode(e) # attempt strict decoding
40 u = s.decode(e) # attempt strict decoding
41 return u.encode(_encoding, "replace")
41 return u.encode(_encoding, "replace")
42 except LookupError, k:
42 except LookupError, k:
43 raise Abort(_("%s, please check your locale settings") % k)
43 raise Abort(_("%s, please check your locale settings") % k)
44 except UnicodeDecodeError:
44 except UnicodeDecodeError:
45 pass
45 pass
46 u = s.decode("utf-8", "replace") # last ditch
46 u = s.decode("utf-8", "replace") # last ditch
47 return u.encode(_encoding, "replace")
47 return u.encode(_encoding, "replace")
48
48
49 def fromlocal(s):
49 def fromlocal(s):
50 """
50 """
51 Convert a string from the local character encoding to UTF-8
51 Convert a string from the local character encoding to UTF-8
52
52
53 We attempt to decode strings using the encoding mode set by
53 We attempt to decode strings using the encoding mode set by
54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
55 characters will cause an error message. Other modes include
55 characters will cause an error message. Other modes include
56 'replace', which replaces unknown characters with a special
56 'replace', which replaces unknown characters with a special
57 Unicode character, and 'ignore', which drops the character.
57 Unicode character, and 'ignore', which drops the character.
58 """
58 """
59 try:
59 try:
60 return s.decode(_encoding, _encodingmode).encode("utf-8")
60 return s.decode(_encoding, _encodingmode).encode("utf-8")
61 except UnicodeDecodeError, inst:
61 except UnicodeDecodeError, inst:
62 sub = s[max(0, inst.start-10):inst.start+10]
62 sub = s[max(0, inst.start-10):inst.start+10]
63 raise Abort("decoding near '%s': %s!" % (sub, inst))
63 raise Abort("decoding near '%s': %s!" % (sub, inst))
64 except LookupError, k:
64 except LookupError, k:
65 raise Abort(_("%s, please check your locale settings") % k)
65 raise Abort(_("%s, please check your locale settings") % k)
66
66
67 def locallen(s):
67 def locallen(s):
68 """Find the length in characters of a local string"""
68 """Find the length in characters of a local string"""
69 return len(s.decode(_encoding, "replace"))
69 return len(s.decode(_encoding, "replace"))
70
70
71 def localsub(s, a, b=None):
71 def localsub(s, a, b=None):
72 try:
72 try:
73 u = s.decode(_encoding, _encodingmode)
73 u = s.decode(_encoding, _encodingmode)
74 if b is not None:
74 if b is not None:
75 u = u[a:b]
75 u = u[a:b]
76 else:
76 else:
77 u = u[:a]
77 u = u[:a]
78 return u.encode(_encoding, _encodingmode)
78 return u.encode(_encoding, _encodingmode)
79 except UnicodeDecodeError, inst:
79 except UnicodeDecodeError, inst:
80 sub = s[max(0, inst.start-10), inst.start+10]
80 sub = s[max(0, inst.start-10), inst.start+10]
81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%b %d %H:%M:%S %Y',
96 '%b %d %H:%M:%S %Y',
97 '%b %d %I:%M:%S%p %Y',
97 '%b %d %I:%M:%S%p %Y',
98 '%b %d %H:%M:%S',
98 '%b %d %H:%M:%S',
99 '%b %d %I:%M:%S%p',
99 '%b %d %I:%M:%S%p',
100 '%b %d %H:%M',
100 '%b %d %H:%M',
101 '%b %d %I:%M%p',
101 '%b %d %I:%M%p',
102 '%b %d %Y',
102 '%b %d %Y',
103 '%b %d',
103 '%b %d',
104 '%H:%M:%S',
104 '%H:%M:%S',
105 '%I:%M:%SP',
105 '%I:%M:%SP',
106 '%H:%M',
106 '%H:%M',
107 '%I:%M%p',
107 '%I:%M%p',
108 )
108 )
109
109
110 extendeddateformats = defaultdateformats + (
110 extendeddateformats = defaultdateformats + (
111 "%Y",
111 "%Y",
112 "%Y-%m",
112 "%Y-%m",
113 "%b",
113 "%b",
114 "%b %Y",
114 "%b %Y",
115 )
115 )
116
116
117 class SignalInterrupt(Exception):
117 class SignalInterrupt(Exception):
118 """Exception raised on SIGTERM and SIGHUP."""
118 """Exception raised on SIGTERM and SIGHUP."""
119
119
120 # like SafeConfigParser but with case-sensitive keys
120 # like SafeConfigParser but with case-sensitive keys
121 class configparser(ConfigParser.SafeConfigParser):
121 class configparser(ConfigParser.SafeConfigParser):
122 def optionxform(self, optionstr):
122 def optionxform(self, optionstr):
123 return optionstr
123 return optionstr
124
124
125 def cachefunc(func):
125 def cachefunc(func):
126 '''cache the result of function calls'''
126 '''cache the result of function calls'''
127 # XXX doesn't handle keywords args
127 # XXX doesn't handle keywords args
128 cache = {}
128 cache = {}
129 if func.func_code.co_argcount == 1:
129 if func.func_code.co_argcount == 1:
130 # we gain a small amount of time because
130 # we gain a small amount of time because
131 # we don't need to pack/unpack the list
131 # we don't need to pack/unpack the list
132 def f(arg):
132 def f(arg):
133 if arg not in cache:
133 if arg not in cache:
134 cache[arg] = func(arg)
134 cache[arg] = func(arg)
135 return cache[arg]
135 return cache[arg]
136 else:
136 else:
137 def f(*args):
137 def f(*args):
138 if args not in cache:
138 if args not in cache:
139 cache[args] = func(*args)
139 cache[args] = func(*args)
140 return cache[args]
140 return cache[args]
141
141
142 return f
142 return f
143
143
144 def pipefilter(s, cmd):
144 def pipefilter(s, cmd):
145 '''filter string S through command CMD, returning its output'''
145 '''filter string S through command CMD, returning its output'''
146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
147 def writer():
147 def writer():
148 try:
148 try:
149 pin.write(s)
149 pin.write(s)
150 pin.close()
150 pin.close()
151 except IOError, inst:
151 except IOError, inst:
152 if inst.errno != errno.EPIPE:
152 if inst.errno != errno.EPIPE:
153 raise
153 raise
154
154
155 # we should use select instead on UNIX, but this will work on most
155 # we should use select instead on UNIX, but this will work on most
156 # systems, including Windows
156 # systems, including Windows
157 w = threading.Thread(target=writer)
157 w = threading.Thread(target=writer)
158 w.start()
158 w.start()
159 f = pout.read()
159 f = pout.read()
160 pout.close()
160 pout.close()
161 w.join()
161 w.join()
162 return f
162 return f
163
163
164 def tempfilter(s, cmd):
164 def tempfilter(s, cmd):
165 '''filter string S through a pair of temporary files with CMD.
165 '''filter string S through a pair of temporary files with CMD.
166 CMD is used as a template to create the real command to be run,
166 CMD is used as a template to create the real command to be run,
167 with the strings INFILE and OUTFILE replaced by the real names of
167 with the strings INFILE and OUTFILE replaced by the real names of
168 the temporary files generated.'''
168 the temporary files generated.'''
169 inname, outname = None, None
169 inname, outname = None, None
170 try:
170 try:
171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
172 fp = os.fdopen(infd, 'wb')
172 fp = os.fdopen(infd, 'wb')
173 fp.write(s)
173 fp.write(s)
174 fp.close()
174 fp.close()
175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
176 os.close(outfd)
176 os.close(outfd)
177 cmd = cmd.replace('INFILE', inname)
177 cmd = cmd.replace('INFILE', inname)
178 cmd = cmd.replace('OUTFILE', outname)
178 cmd = cmd.replace('OUTFILE', outname)
179 code = os.system(cmd)
179 code = os.system(cmd)
180 if code: raise Abort(_("command '%s' failed: %s") %
180 if code: raise Abort(_("command '%s' failed: %s") %
181 (cmd, explain_exit(code)))
181 (cmd, explain_exit(code)))
182 return open(outname, 'rb').read()
182 return open(outname, 'rb').read()
183 finally:
183 finally:
184 try:
184 try:
185 if inname: os.unlink(inname)
185 if inname: os.unlink(inname)
186 except: pass
186 except: pass
187 try:
187 try:
188 if outname: os.unlink(outname)
188 if outname: os.unlink(outname)
189 except: pass
189 except: pass
190
190
191 filtertable = {
191 filtertable = {
192 'tempfile:': tempfilter,
192 'tempfile:': tempfilter,
193 'pipe:': pipefilter,
193 'pipe:': pipefilter,
194 }
194 }
195
195
196 def filter(s, cmd):
196 def filter(s, cmd):
197 "filter a string through a command that transforms its input to its output"
197 "filter a string through a command that transforms its input to its output"
198 for name, fn in filtertable.iteritems():
198 for name, fn in filtertable.iteritems():
199 if cmd.startswith(name):
199 if cmd.startswith(name):
200 return fn(s, cmd[len(name):].lstrip())
200 return fn(s, cmd[len(name):].lstrip())
201 return pipefilter(s, cmd)
201 return pipefilter(s, cmd)
202
202
203 def find_in_path(name, path, default=None):
203 def find_in_path(name, path, default=None):
204 '''find name in search path. path can be string (will be split
204 '''find name in search path. path can be string (will be split
205 with os.pathsep), or iterable thing that returns strings. if name
205 with os.pathsep), or iterable thing that returns strings. if name
206 found, return path to name. else return default.'''
206 found, return path to name. else return default.'''
207 if isinstance(path, str):
207 if isinstance(path, str):
208 path = path.split(os.pathsep)
208 path = path.split(os.pathsep)
209 for p in path:
209 for p in path:
210 p_name = os.path.join(p, name)
210 p_name = os.path.join(p, name)
211 if os.path.exists(p_name):
211 if os.path.exists(p_name):
212 return p_name
212 return p_name
213 return default
213 return default
214
214
215 def binary(s):
215 def binary(s):
216 """return true if a string is binary data using diff's heuristic"""
216 """return true if a string is binary data using diff's heuristic"""
217 if s and '\0' in s[:4096]:
217 if s and '\0' in s[:4096]:
218 return True
218 return True
219 return False
219 return False
220
220
221 def unique(g):
221 def unique(g):
222 """return the uniq elements of iterable g"""
222 """return the uniq elements of iterable g"""
223 seen = {}
223 seen = {}
224 l = []
224 l = []
225 for f in g:
225 for f in g:
226 if f not in seen:
226 if f not in seen:
227 seen[f] = 1
227 seen[f] = 1
228 l.append(f)
228 l.append(f)
229 return l
229 return l
230
230
231 class Abort(Exception):
231 class Abort(Exception):
232 """Raised if a command needs to print an error and exit."""
232 """Raised if a command needs to print an error and exit."""
233
233
234 class UnexpectedOutput(Abort):
234 class UnexpectedOutput(Abort):
235 """Raised to print an error with part of output and exit."""
235 """Raised to print an error with part of output and exit."""
236
236
237 def always(fn): return True
237 def always(fn): return True
238 def never(fn): return False
238 def never(fn): return False
239
239
240 def expand_glob(pats):
240 def expand_glob(pats):
241 '''On Windows, expand the implicit globs in a list of patterns'''
241 '''On Windows, expand the implicit globs in a list of patterns'''
242 if os.name != 'nt':
242 if os.name != 'nt':
243 return list(pats)
243 return list(pats)
244 ret = []
244 ret = []
245 for p in pats:
245 for p in pats:
246 kind, name = patkind(p, None)
246 kind, name = patkind(p, None)
247 if kind is None:
247 if kind is None:
248 globbed = glob.glob(name)
248 globbed = glob.glob(name)
249 if globbed:
249 if globbed:
250 ret.extend(globbed)
250 ret.extend(globbed)
251 continue
251 continue
252 # if we couldn't expand the glob, just keep it around
252 # if we couldn't expand the glob, just keep it around
253 ret.append(p)
253 ret.append(p)
254 return ret
254 return ret
255
255
256 def patkind(name, dflt_pat='glob'):
256 def patkind(name, dflt_pat='glob'):
257 """Split a string into an optional pattern kind prefix and the
257 """Split a string into an optional pattern kind prefix and the
258 actual pattern."""
258 actual pattern."""
259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
260 if name.startswith(prefix + ':'): return name.split(':', 1)
260 if name.startswith(prefix + ':'): return name.split(':', 1)
261 return dflt_pat, name
261 return dflt_pat, name
262
262
263 def globre(pat, head='^', tail='$'):
263 def globre(pat, head='^', tail='$'):
264 "convert a glob pattern into a regexp"
264 "convert a glob pattern into a regexp"
265 i, n = 0, len(pat)
265 i, n = 0, len(pat)
266 res = ''
266 res = ''
267 group = False
267 group = False
268 def peek(): return i < n and pat[i]
268 def peek(): return i < n and pat[i]
269 while i < n:
269 while i < n:
270 c = pat[i]
270 c = pat[i]
271 i = i+1
271 i = i+1
272 if c == '*':
272 if c == '*':
273 if peek() == '*':
273 if peek() == '*':
274 i += 1
274 i += 1
275 res += '.*'
275 res += '.*'
276 else:
276 else:
277 res += '[^/]*'
277 res += '[^/]*'
278 elif c == '?':
278 elif c == '?':
279 res += '.'
279 res += '.'
280 elif c == '[':
280 elif c == '[':
281 j = i
281 j = i
282 if j < n and pat[j] in '!]':
282 if j < n and pat[j] in '!]':
283 j += 1
283 j += 1
284 while j < n and pat[j] != ']':
284 while j < n and pat[j] != ']':
285 j += 1
285 j += 1
286 if j >= n:
286 if j >= n:
287 res += '\\['
287 res += '\\['
288 else:
288 else:
289 stuff = pat[i:j].replace('\\','\\\\')
289 stuff = pat[i:j].replace('\\','\\\\')
290 i = j + 1
290 i = j + 1
291 if stuff[0] == '!':
291 if stuff[0] == '!':
292 stuff = '^' + stuff[1:]
292 stuff = '^' + stuff[1:]
293 elif stuff[0] == '^':
293 elif stuff[0] == '^':
294 stuff = '\\' + stuff
294 stuff = '\\' + stuff
295 res = '%s[%s]' % (res, stuff)
295 res = '%s[%s]' % (res, stuff)
296 elif c == '{':
296 elif c == '{':
297 group = True
297 group = True
298 res += '(?:'
298 res += '(?:'
299 elif c == '}' and group:
299 elif c == '}' and group:
300 res += ')'
300 res += ')'
301 group = False
301 group = False
302 elif c == ',' and group:
302 elif c == ',' and group:
303 res += '|'
303 res += '|'
304 elif c == '\\':
304 elif c == '\\':
305 p = peek()
305 p = peek()
306 if p:
306 if p:
307 i += 1
307 i += 1
308 res += re.escape(p)
308 res += re.escape(p)
309 else:
309 else:
310 res += re.escape(c)
310 res += re.escape(c)
311 else:
311 else:
312 res += re.escape(c)
312 res += re.escape(c)
313 return head + res + tail
313 return head + res + tail
314
314
315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
316
316
317 def pathto(n1, n2):
317 def pathto(n1, n2):
318 '''return the relative path from one place to another.
318 '''return the relative path from one place to another.
319 n1 should use os.sep to separate directories
319 n1 should use os.sep to separate directories
320 n2 should use "/" to separate directories
320 n2 should use "/" to separate directories
321 returns an os.sep-separated path.
321 returns an os.sep-separated path.
322 '''
322 '''
323 if not n1: return localpath(n2)
323 if not n1: return localpath(n2)
324 a, b = n1.split(os.sep), n2.split('/')
324 a, b = n1.split(os.sep), n2.split('/')
325 a.reverse()
325 a.reverse()
326 b.reverse()
326 b.reverse()
327 while a and b and a[-1] == b[-1]:
327 while a and b and a[-1] == b[-1]:
328 a.pop()
328 a.pop()
329 b.pop()
329 b.pop()
330 b.reverse()
330 b.reverse()
331 return os.sep.join((['..'] * len(a)) + b)
331 return os.sep.join((['..'] * len(a)) + b)
332
332
333 def canonpath(root, cwd, myname):
333 def canonpath(root, cwd, myname):
334 """return the canonical path of myname, given cwd and root"""
334 """return the canonical path of myname, given cwd and root"""
335 if root == os.sep:
335 if root == os.sep:
336 rootsep = os.sep
336 rootsep = os.sep
337 elif root.endswith(os.sep):
337 elif root.endswith(os.sep):
338 rootsep = root
338 rootsep = root
339 else:
339 else:
340 rootsep = root + os.sep
340 rootsep = root + os.sep
341 name = myname
341 name = myname
342 if not os.path.isabs(name):
342 if not os.path.isabs(name):
343 name = os.path.join(root, cwd, name)
343 name = os.path.join(root, cwd, name)
344 name = os.path.normpath(name)
344 name = os.path.normpath(name)
345 if name != rootsep and name.startswith(rootsep):
345 if name != rootsep and name.startswith(rootsep):
346 name = name[len(rootsep):]
346 name = name[len(rootsep):]
347 audit_path(name)
347 audit_path(name)
348 return pconvert(name)
348 return pconvert(name)
349 elif name == root:
349 elif name == root:
350 return ''
350 return ''
351 else:
351 else:
352 # Determine whether `name' is in the hierarchy at or beneath `root',
352 # Determine whether `name' is in the hierarchy at or beneath `root',
353 # by iterating name=dirname(name) until that causes no change (can't
353 # by iterating name=dirname(name) until that causes no change (can't
354 # check name == '/', because that doesn't work on windows). For each
354 # check name == '/', because that doesn't work on windows). For each
355 # `name', compare dev/inode numbers. If they match, the list `rel'
355 # `name', compare dev/inode numbers. If they match, the list `rel'
356 # holds the reversed list of components making up the relative file
356 # holds the reversed list of components making up the relative file
357 # name we want.
357 # name we want.
358 root_st = os.stat(root)
358 root_st = os.stat(root)
359 rel = []
359 rel = []
360 while True:
360 while True:
361 try:
361 try:
362 name_st = os.stat(name)
362 name_st = os.stat(name)
363 except OSError:
363 except OSError:
364 break
364 break
365 if samestat(name_st, root_st):
365 if samestat(name_st, root_st):
366 if not rel:
366 if not rel:
367 # name was actually the same as root (maybe a symlink)
367 # name was actually the same as root (maybe a symlink)
368 return ''
368 return ''
369 rel.reverse()
369 rel.reverse()
370 name = os.path.join(*rel)
370 name = os.path.join(*rel)
371 audit_path(name)
371 audit_path(name)
372 return pconvert(name)
372 return pconvert(name)
373 dirname, basename = os.path.split(name)
373 dirname, basename = os.path.split(name)
374 rel.append(basename)
374 rel.append(basename)
375 if dirname == name:
375 if dirname == name:
376 break
376 break
377 name = dirname
377 name = dirname
378
378
379 raise Abort('%s not under root' % myname)
379 raise Abort('%s not under root' % myname)
380
380
381 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], head='', src=None):
381 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], head='', src=None):
382 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
382 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
383
383
384 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], head='',
384 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], head='',
385 src=None, globbed=False):
385 src=None, globbed=False):
386 if not globbed:
386 if not globbed:
387 names = expand_glob(names)
387 names = expand_glob(names)
388 return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
388 return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
389
389
390 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
390 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
391 """build a function to match a set of file patterns
391 """build a function to match a set of file patterns
392
392
393 arguments:
393 arguments:
394 canonroot - the canonical root of the tree you're matching against
394 canonroot - the canonical root of the tree you're matching against
395 cwd - the current working directory, if relevant
395 cwd - the current working directory, if relevant
396 names - patterns to find
396 names - patterns to find
397 inc - patterns to include
397 inc - patterns to include
398 exc - patterns to exclude
398 exc - patterns to exclude
399 head - a regex to prepend to patterns to control whether a match is rooted
399 head - a regex to prepend to patterns to control whether a match is rooted
400 dflt_pat - if a pattern in names has no explicit type, assume this one
400 dflt_pat - if a pattern in names has no explicit type, assume this one
401 src - where these patterns came from (e.g. .hgignore)
401 src - where these patterns came from (e.g. .hgignore)
402
402
403 a pattern is one of:
403 a pattern is one of:
404 'glob:<glob>' - a glob relative to cwd
404 'glob:<glob>' - a glob relative to cwd
405 're:<regexp>' - a regular expression
405 're:<regexp>' - a regular expression
406 'path:<path>' - a path relative to canonroot
406 'path:<path>' - a path relative to canonroot
407 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
407 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
408 'relpath:<path>' - a path relative to cwd
408 'relpath:<path>' - a path relative to cwd
409 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
409 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
410 '<something>' - one of the cases above, selected by the dflt_pat argument
410 '<something>' - one of the cases above, selected by the dflt_pat argument
411
411
412 returns:
412 returns:
413 a 3-tuple containing
413 a 3-tuple containing
414 - list of roots (places where one should start a recursive walk of the fs);
414 - list of roots (places where one should start a recursive walk of the fs);
415 this often matches the explicit non-pattern names passed in, but also
415 this often matches the explicit non-pattern names passed in, but also
416 includes the initial part of glob: patterns that has no glob characters
416 includes the initial part of glob: patterns that has no glob characters
417 - a bool match(filename) function
417 - a bool match(filename) function
418 - a bool indicating if any patterns were passed in
418 - a bool indicating if any patterns were passed in
419
419
420 todo:
420 todo:
421 make head regex a rooted bool
421 make head regex a rooted bool
422 """
422 """
423
423
424 def contains_glob(name):
424 def contains_glob(name):
425 for c in name:
425 for c in name:
426 if c in _globchars: return True
426 if c in _globchars: return True
427 return False
427 return False
428
428
429 def regex(kind, name, tail):
429 def regex(kind, name, tail):
430 '''convert a pattern into a regular expression'''
430 '''convert a pattern into a regular expression'''
431 if not name:
431 if not name:
432 return ''
432 return ''
433 if kind == 're':
433 if kind == 're':
434 return name
434 return name
435 elif kind == 'path':
435 elif kind == 'path':
436 return '^' + re.escape(name) + '(?:/|$)'
436 return '^' + re.escape(name) + '(?:/|$)'
437 elif kind == 'relglob':
437 elif kind == 'relglob':
438 return head + globre(name, '(?:|.*/)', '(?:/|$)')
438 return head + globre(name, '(?:|.*/)', '(?:/|$)')
439 elif kind == 'relpath':
439 elif kind == 'relpath':
440 return head + re.escape(name) + '(?:/|$)'
440 return head + re.escape(name) + '(?:/|$)'
441 elif kind == 'relre':
441 elif kind == 'relre':
442 if name.startswith('^'):
442 if name.startswith('^'):
443 return name
443 return name
444 return '.*' + name
444 return '.*' + name
445 return head + globre(name, '', tail)
445 return head + globre(name, '', tail)
446
446
447 def matchfn(pats, tail):
447 def matchfn(pats, tail):
448 """build a matching function from a set of patterns"""
448 """build a matching function from a set of patterns"""
449 if not pats:
449 if not pats:
450 return
450 return
451 matches = []
451 matches = []
452 for k, p in pats:
452 for k, p in pats:
453 try:
453 try:
454 pat = '(?:%s)' % regex(k, p, tail)
454 pat = '(?:%s)' % regex(k, p, tail)
455 matches.append(re.compile(pat).match)
455 matches.append(re.compile(pat).match)
456 except re.error:
456 except re.error:
457 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
457 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
458 else: raise Abort("invalid pattern (%s): %s" % (k, p))
458 else: raise Abort("invalid pattern (%s): %s" % (k, p))
459
459
460 def buildfn(text):
460 def buildfn(text):
461 for m in matches:
461 for m in matches:
462 r = m(text)
462 r = m(text)
463 if r:
463 if r:
464 return r
464 return r
465
465
466 return buildfn
466 return buildfn
467
467
468 def globprefix(pat):
468 def globprefix(pat):
469 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
469 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
470 root = []
470 root = []
471 for p in pat.split('/'):
471 for p in pat.split('/'):
472 if contains_glob(p): break
472 if contains_glob(p): break
473 root.append(p)
473 root.append(p)
474 return '/'.join(root) or '.'
474 return '/'.join(root) or '.'
475
475
476 def normalizepats(names, default):
476 def normalizepats(names, default):
477 pats = []
477 pats = []
478 files = []
478 files = []
479 roots = []
479 roots = []
480 anypats = False
480 anypats = False
481 for kind, name in [patkind(p, default) for p in names]:
481 for kind, name in [patkind(p, default) for p in names]:
482 if kind in ('glob', 'relpath'):
482 if kind in ('glob', 'relpath'):
483 name = canonpath(canonroot, cwd, name)
483 name = canonpath(canonroot, cwd, name)
484 elif kind in ('relglob', 'path'):
484 elif kind in ('relglob', 'path'):
485 name = normpath(name)
485 name = normpath(name)
486 if kind in ('glob', 're', 'relglob', 'relre'):
486 if kind in ('glob', 're', 'relglob', 'relre'):
487 pats.append((kind, name))
487 pats.append((kind, name))
488 anypats = True
488 anypats = True
489 if kind == 'glob':
489 if kind == 'glob':
490 root = globprefix(name)
490 root = globprefix(name)
491 roots.append(root)
491 roots.append(root)
492 elif kind in ('relpath', 'path'):
492 elif kind in ('relpath', 'path'):
493 files.append((kind, name))
493 files.append((kind, name))
494 roots.append(name)
494 roots.append(name)
495 elif kind == 'relglob':
495 elif kind == 'relglob':
496 roots.append('.')
496 roots.append('.')
497 return roots, pats + files, anypats
497 return roots, pats + files, anypats
498
498
499 roots, pats, anypats = normalizepats(names, dflt_pat)
499 roots, pats, anypats = normalizepats(names, dflt_pat)
500
500
501 patmatch = matchfn(pats, '$') or always
501 patmatch = matchfn(pats, '$') or always
502 incmatch = always
502 incmatch = always
503 if inc:
503 if inc:
504 dummy, inckinds, dummy = normalizepats(inc, 'glob')
504 dummy, inckinds, dummy = normalizepats(inc, 'glob')
505 incmatch = matchfn(inckinds, '(?:/|$)')
505 incmatch = matchfn(inckinds, '(?:/|$)')
506 excmatch = lambda fn: False
506 excmatch = lambda fn: False
507 if exc:
507 if exc:
508 dummy, exckinds, dummy = normalizepats(exc, 'glob')
508 dummy, exckinds, dummy = normalizepats(exc, 'glob')
509 excmatch = matchfn(exckinds, '(?:/|$)')
509 excmatch = matchfn(exckinds, '(?:/|$)')
510
510
511 return (roots,
511 return (roots,
512 lambda fn: (incmatch(fn) and not excmatch(fn) and
512 lambda fn: (incmatch(fn) and not excmatch(fn) and patmatch(fn)),
513 (fn.endswith('/') or patmatch(fn))),
514 (inc or exc or anypats) and True)
513 (inc or exc or anypats) and True)
515
514
516 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
515 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
517 '''enhanced shell command execution.
516 '''enhanced shell command execution.
518 run with environment maybe modified, maybe in different dir.
517 run with environment maybe modified, maybe in different dir.
519
518
520 if command fails and onerr is None, return status. if ui object,
519 if command fails and onerr is None, return status. if ui object,
521 print error message and return status, else raise onerr object as
520 print error message and return status, else raise onerr object as
522 exception.'''
521 exception.'''
523 def py2shell(val):
522 def py2shell(val):
524 'convert python object into string that is useful to shell'
523 'convert python object into string that is useful to shell'
525 if val in (None, False):
524 if val in (None, False):
526 return '0'
525 return '0'
527 if val == True:
526 if val == True:
528 return '1'
527 return '1'
529 return str(val)
528 return str(val)
530 oldenv = {}
529 oldenv = {}
531 for k in environ:
530 for k in environ:
532 oldenv[k] = os.environ.get(k)
531 oldenv[k] = os.environ.get(k)
533 if cwd is not None:
532 if cwd is not None:
534 oldcwd = os.getcwd()
533 oldcwd = os.getcwd()
535 origcmd = cmd
534 origcmd = cmd
536 if os.name == 'nt':
535 if os.name == 'nt':
537 cmd = '"%s"' % cmd
536 cmd = '"%s"' % cmd
538 try:
537 try:
539 for k, v in environ.iteritems():
538 for k, v in environ.iteritems():
540 os.environ[k] = py2shell(v)
539 os.environ[k] = py2shell(v)
541 if cwd is not None and oldcwd != cwd:
540 if cwd is not None and oldcwd != cwd:
542 os.chdir(cwd)
541 os.chdir(cwd)
543 rc = os.system(cmd)
542 rc = os.system(cmd)
544 if rc and onerr:
543 if rc and onerr:
545 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
544 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
546 explain_exit(rc)[0])
545 explain_exit(rc)[0])
547 if errprefix:
546 if errprefix:
548 errmsg = '%s: %s' % (errprefix, errmsg)
547 errmsg = '%s: %s' % (errprefix, errmsg)
549 try:
548 try:
550 onerr.warn(errmsg + '\n')
549 onerr.warn(errmsg + '\n')
551 except AttributeError:
550 except AttributeError:
552 raise onerr(errmsg)
551 raise onerr(errmsg)
553 return rc
552 return rc
554 finally:
553 finally:
555 for k, v in oldenv.iteritems():
554 for k, v in oldenv.iteritems():
556 if v is None:
555 if v is None:
557 del os.environ[k]
556 del os.environ[k]
558 else:
557 else:
559 os.environ[k] = v
558 os.environ[k] = v
560 if cwd is not None and oldcwd != cwd:
559 if cwd is not None and oldcwd != cwd:
561 os.chdir(oldcwd)
560 os.chdir(oldcwd)
562
561
563 def rename(src, dst):
562 def rename(src, dst):
564 """forcibly rename a file"""
563 """forcibly rename a file"""
565 try:
564 try:
566 os.rename(src, dst)
565 os.rename(src, dst)
567 except OSError, err:
566 except OSError, err:
568 # on windows, rename to existing file is not allowed, so we
567 # on windows, rename to existing file is not allowed, so we
569 # must delete destination first. but if file is open, unlink
568 # must delete destination first. but if file is open, unlink
570 # schedules it for delete but does not delete it. rename
569 # schedules it for delete but does not delete it. rename
571 # happens immediately even for open files, so we create
570 # happens immediately even for open files, so we create
572 # temporary file, delete it, rename destination to that name,
571 # temporary file, delete it, rename destination to that name,
573 # then delete that. then rename is safe to do.
572 # then delete that. then rename is safe to do.
574 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
573 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
575 os.close(fd)
574 os.close(fd)
576 os.unlink(temp)
575 os.unlink(temp)
577 os.rename(dst, temp)
576 os.rename(dst, temp)
578 os.unlink(temp)
577 os.unlink(temp)
579 os.rename(src, dst)
578 os.rename(src, dst)
580
579
581 def unlink(f):
580 def unlink(f):
582 """unlink and remove the directory if it is empty"""
581 """unlink and remove the directory if it is empty"""
583 os.unlink(f)
582 os.unlink(f)
584 # try removing directories that might now be empty
583 # try removing directories that might now be empty
585 try:
584 try:
586 os.removedirs(os.path.dirname(f))
585 os.removedirs(os.path.dirname(f))
587 except OSError:
586 except OSError:
588 pass
587 pass
589
588
590 def copyfile(src, dest):
589 def copyfile(src, dest):
591 "copy a file, preserving mode"
590 "copy a file, preserving mode"
592 try:
591 try:
593 shutil.copyfile(src, dest)
592 shutil.copyfile(src, dest)
594 shutil.copymode(src, dest)
593 shutil.copymode(src, dest)
595 except shutil.Error, inst:
594 except shutil.Error, inst:
596 raise Abort(str(inst))
595 raise Abort(str(inst))
597
596
598 def copyfiles(src, dst, hardlink=None):
597 def copyfiles(src, dst, hardlink=None):
599 """Copy a directory tree using hardlinks if possible"""
598 """Copy a directory tree using hardlinks if possible"""
600
599
601 if hardlink is None:
600 if hardlink is None:
602 hardlink = (os.stat(src).st_dev ==
601 hardlink = (os.stat(src).st_dev ==
603 os.stat(os.path.dirname(dst)).st_dev)
602 os.stat(os.path.dirname(dst)).st_dev)
604
603
605 if os.path.isdir(src):
604 if os.path.isdir(src):
606 os.mkdir(dst)
605 os.mkdir(dst)
607 for name in os.listdir(src):
606 for name in os.listdir(src):
608 srcname = os.path.join(src, name)
607 srcname = os.path.join(src, name)
609 dstname = os.path.join(dst, name)
608 dstname = os.path.join(dst, name)
610 copyfiles(srcname, dstname, hardlink)
609 copyfiles(srcname, dstname, hardlink)
611 else:
610 else:
612 if hardlink:
611 if hardlink:
613 try:
612 try:
614 os_link(src, dst)
613 os_link(src, dst)
615 except (IOError, OSError):
614 except (IOError, OSError):
616 hardlink = False
615 hardlink = False
617 shutil.copy(src, dst)
616 shutil.copy(src, dst)
618 else:
617 else:
619 shutil.copy(src, dst)
618 shutil.copy(src, dst)
620
619
621 def audit_path(path):
620 def audit_path(path):
622 """Abort if path contains dangerous components"""
621 """Abort if path contains dangerous components"""
623 parts = os.path.normcase(path).split(os.sep)
622 parts = os.path.normcase(path).split(os.sep)
624 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
623 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
625 or os.pardir in parts):
624 or os.pardir in parts):
626 raise Abort(_("path contains illegal component: %s\n") % path)
625 raise Abort(_("path contains illegal component: %s\n") % path)
627
626
628 def _makelock_file(info, pathname):
627 def _makelock_file(info, pathname):
629 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
628 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
630 os.write(ld, info)
629 os.write(ld, info)
631 os.close(ld)
630 os.close(ld)
632
631
633 def _readlock_file(pathname):
632 def _readlock_file(pathname):
634 return posixfile(pathname).read()
633 return posixfile(pathname).read()
635
634
636 def nlinks(pathname):
635 def nlinks(pathname):
637 """Return number of hardlinks for the given file."""
636 """Return number of hardlinks for the given file."""
638 return os.lstat(pathname).st_nlink
637 return os.lstat(pathname).st_nlink
639
638
640 if hasattr(os, 'link'):
639 if hasattr(os, 'link'):
641 os_link = os.link
640 os_link = os.link
642 else:
641 else:
643 def os_link(src, dst):
642 def os_link(src, dst):
644 raise OSError(0, _("Hardlinks not supported"))
643 raise OSError(0, _("Hardlinks not supported"))
645
644
646 def fstat(fp):
645 def fstat(fp):
647 '''stat file object that may not have fileno method.'''
646 '''stat file object that may not have fileno method.'''
648 try:
647 try:
649 return os.fstat(fp.fileno())
648 return os.fstat(fp.fileno())
650 except AttributeError:
649 except AttributeError:
651 return os.stat(fp.name)
650 return os.stat(fp.name)
652
651
653 posixfile = file
652 posixfile = file
654
653
655 def is_win_9x():
654 def is_win_9x():
656 '''return true if run on windows 95, 98 or me.'''
655 '''return true if run on windows 95, 98 or me.'''
657 try:
656 try:
658 return sys.getwindowsversion()[3] == 1
657 return sys.getwindowsversion()[3] == 1
659 except AttributeError:
658 except AttributeError:
660 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
659 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
661
660
662 getuser_fallback = None
661 getuser_fallback = None
663
662
664 def getuser():
663 def getuser():
665 '''return name of current user'''
664 '''return name of current user'''
666 try:
665 try:
667 return getpass.getuser()
666 return getpass.getuser()
668 except ImportError:
667 except ImportError:
669 # import of pwd will fail on windows - try fallback
668 # import of pwd will fail on windows - try fallback
670 if getuser_fallback:
669 if getuser_fallback:
671 return getuser_fallback()
670 return getuser_fallback()
672 # raised if win32api not available
671 # raised if win32api not available
673 raise Abort(_('user name not available - set USERNAME '
672 raise Abort(_('user name not available - set USERNAME '
674 'environment variable'))
673 'environment variable'))
675
674
676 def username(uid=None):
675 def username(uid=None):
677 """Return the name of the user with the given uid.
676 """Return the name of the user with the given uid.
678
677
679 If uid is None, return the name of the current user."""
678 If uid is None, return the name of the current user."""
680 try:
679 try:
681 import pwd
680 import pwd
682 if uid is None:
681 if uid is None:
683 uid = os.getuid()
682 uid = os.getuid()
684 try:
683 try:
685 return pwd.getpwuid(uid)[0]
684 return pwd.getpwuid(uid)[0]
686 except KeyError:
685 except KeyError:
687 return str(uid)
686 return str(uid)
688 except ImportError:
687 except ImportError:
689 return None
688 return None
690
689
691 def groupname(gid=None):
690 def groupname(gid=None):
692 """Return the name of the group with the given gid.
691 """Return the name of the group with the given gid.
693
692
694 If gid is None, return the name of the current group."""
693 If gid is None, return the name of the current group."""
695 try:
694 try:
696 import grp
695 import grp
697 if gid is None:
696 if gid is None:
698 gid = os.getgid()
697 gid = os.getgid()
699 try:
698 try:
700 return grp.getgrgid(gid)[0]
699 return grp.getgrgid(gid)[0]
701 except KeyError:
700 except KeyError:
702 return str(gid)
701 return str(gid)
703 except ImportError:
702 except ImportError:
704 return None
703 return None
705
704
706 # File system features
705 # File system features
707
706
708 def checkfolding(path):
707 def checkfolding(path):
709 """
708 """
710 Check whether the given path is on a case-sensitive filesystem
709 Check whether the given path is on a case-sensitive filesystem
711
710
712 Requires a path (like /foo/.hg) ending with a foldable final
711 Requires a path (like /foo/.hg) ending with a foldable final
713 directory component.
712 directory component.
714 """
713 """
715 s1 = os.stat(path)
714 s1 = os.stat(path)
716 d, b = os.path.split(path)
715 d, b = os.path.split(path)
717 p2 = os.path.join(d, b.upper())
716 p2 = os.path.join(d, b.upper())
718 if path == p2:
717 if path == p2:
719 p2 = os.path.join(d, b.lower())
718 p2 = os.path.join(d, b.lower())
720 try:
719 try:
721 s2 = os.stat(p2)
720 s2 = os.stat(p2)
722 if s2 == s1:
721 if s2 == s1:
723 return False
722 return False
724 return True
723 return True
725 except:
724 except:
726 return True
725 return True
727
726
728 # Platform specific variants
727 # Platform specific variants
729 if os.name == 'nt':
728 if os.name == 'nt':
730 demandload(globals(), "msvcrt")
729 demandload(globals(), "msvcrt")
731 nulldev = 'NUL:'
730 nulldev = 'NUL:'
732
731
733 class winstdout:
732 class winstdout:
734 '''stdout on windows misbehaves if sent through a pipe'''
733 '''stdout on windows misbehaves if sent through a pipe'''
735
734
736 def __init__(self, fp):
735 def __init__(self, fp):
737 self.fp = fp
736 self.fp = fp
738
737
739 def __getattr__(self, key):
738 def __getattr__(self, key):
740 return getattr(self.fp, key)
739 return getattr(self.fp, key)
741
740
742 def close(self):
741 def close(self):
743 try:
742 try:
744 self.fp.close()
743 self.fp.close()
745 except: pass
744 except: pass
746
745
747 def write(self, s):
746 def write(self, s):
748 try:
747 try:
749 return self.fp.write(s)
748 return self.fp.write(s)
750 except IOError, inst:
749 except IOError, inst:
751 if inst.errno != 0: raise
750 if inst.errno != 0: raise
752 self.close()
751 self.close()
753 raise IOError(errno.EPIPE, 'Broken pipe')
752 raise IOError(errno.EPIPE, 'Broken pipe')
754
753
755 def flush(self):
754 def flush(self):
756 try:
755 try:
757 return self.fp.flush()
756 return self.fp.flush()
758 except IOError, inst:
757 except IOError, inst:
759 if inst.errno != errno.EINVAL: raise
758 if inst.errno != errno.EINVAL: raise
760 self.close()
759 self.close()
761 raise IOError(errno.EPIPE, 'Broken pipe')
760 raise IOError(errno.EPIPE, 'Broken pipe')
762
761
763 sys.stdout = winstdout(sys.stdout)
762 sys.stdout = winstdout(sys.stdout)
764
763
765 def system_rcpath():
764 def system_rcpath():
766 try:
765 try:
767 return system_rcpath_win32()
766 return system_rcpath_win32()
768 except:
767 except:
769 return [r'c:\mercurial\mercurial.ini']
768 return [r'c:\mercurial\mercurial.ini']
770
769
771 def os_rcpath():
770 def os_rcpath():
772 '''return default os-specific hgrc search path'''
771 '''return default os-specific hgrc search path'''
773 path = system_rcpath()
772 path = system_rcpath()
774 path.append(user_rcpath())
773 path.append(user_rcpath())
775 userprofile = os.environ.get('USERPROFILE')
774 userprofile = os.environ.get('USERPROFILE')
776 if userprofile:
775 if userprofile:
777 path.append(os.path.join(userprofile, 'mercurial.ini'))
776 path.append(os.path.join(userprofile, 'mercurial.ini'))
778 return path
777 return path
779
778
780 def user_rcpath():
779 def user_rcpath():
781 '''return os-specific hgrc search path to the user dir'''
780 '''return os-specific hgrc search path to the user dir'''
782 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
781 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
783
782
784 def parse_patch_output(output_line):
783 def parse_patch_output(output_line):
785 """parses the output produced by patch and returns the file name"""
784 """parses the output produced by patch and returns the file name"""
786 pf = output_line[14:]
785 pf = output_line[14:]
787 if pf[0] == '`':
786 if pf[0] == '`':
788 pf = pf[1:-1] # Remove the quotes
787 pf = pf[1:-1] # Remove the quotes
789 return pf
788 return pf
790
789
791 def testpid(pid):
790 def testpid(pid):
792 '''return False if pid dead, True if running or not known'''
791 '''return False if pid dead, True if running or not known'''
793 return True
792 return True
794
793
795 def is_exec(f, last):
794 def is_exec(f, last):
796 return last
795 return last
797
796
798 def set_exec(f, mode):
797 def set_exec(f, mode):
799 pass
798 pass
800
799
801 def set_binary(fd):
800 def set_binary(fd):
802 msvcrt.setmode(fd.fileno(), os.O_BINARY)
801 msvcrt.setmode(fd.fileno(), os.O_BINARY)
803
802
804 def pconvert(path):
803 def pconvert(path):
805 return path.replace("\\", "/")
804 return path.replace("\\", "/")
806
805
807 def localpath(path):
806 def localpath(path):
808 return path.replace('/', '\\')
807 return path.replace('/', '\\')
809
808
810 def normpath(path):
809 def normpath(path):
811 return pconvert(os.path.normpath(path))
810 return pconvert(os.path.normpath(path))
812
811
813 makelock = _makelock_file
812 makelock = _makelock_file
814 readlock = _readlock_file
813 readlock = _readlock_file
815
814
816 def samestat(s1, s2):
815 def samestat(s1, s2):
817 return False
816 return False
818
817
819 # A sequence of backslashes is special iff it precedes a double quote:
818 # A sequence of backslashes is special iff it precedes a double quote:
820 # - if there's an even number of backslashes, the double quote is not
819 # - if there's an even number of backslashes, the double quote is not
821 # quoted (i.e. it ends the quoted region)
820 # quoted (i.e. it ends the quoted region)
822 # - if there's an odd number of backslashes, the double quote is quoted
821 # - if there's an odd number of backslashes, the double quote is quoted
823 # - in both cases, every pair of backslashes is unquoted into a single
822 # - in both cases, every pair of backslashes is unquoted into a single
824 # backslash
823 # backslash
825 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
824 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
826 # So, to quote a string, we must surround it in double quotes, double
825 # So, to quote a string, we must surround it in double quotes, double
827 # the number of backslashes that preceed double quotes and add another
826 # the number of backslashes that preceed double quotes and add another
828 # backslash before every double quote (being careful with the double
827 # backslash before every double quote (being careful with the double
829 # quote we've appended to the end)
828 # quote we've appended to the end)
830 _quotere = None
829 _quotere = None
831 def shellquote(s):
830 def shellquote(s):
832 global _quotere
831 global _quotere
833 if _quotere is None:
832 if _quotere is None:
834 _quotere = re.compile(r'(\\*)("|\\$)')
833 _quotere = re.compile(r'(\\*)("|\\$)')
835 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
834 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
836
835
837 def explain_exit(code):
836 def explain_exit(code):
838 return _("exited with status %d") % code, code
837 return _("exited with status %d") % code, code
839
838
840 # if you change this stub into a real check, please try to implement the
839 # if you change this stub into a real check, please try to implement the
841 # username and groupname functions above, too.
840 # username and groupname functions above, too.
842 def isowner(fp, st=None):
841 def isowner(fp, st=None):
843 return True
842 return True
844
843
845 try:
844 try:
846 # override functions with win32 versions if possible
845 # override functions with win32 versions if possible
847 from util_win32 import *
846 from util_win32 import *
848 if not is_win_9x():
847 if not is_win_9x():
849 posixfile = posixfile_nt
848 posixfile = posixfile_nt
850 except ImportError:
849 except ImportError:
851 pass
850 pass
852
851
853 else:
852 else:
854 nulldev = '/dev/null'
853 nulldev = '/dev/null'
855
854
856 def rcfiles(path):
855 def rcfiles(path):
857 rcs = [os.path.join(path, 'hgrc')]
856 rcs = [os.path.join(path, 'hgrc')]
858 rcdir = os.path.join(path, 'hgrc.d')
857 rcdir = os.path.join(path, 'hgrc.d')
859 try:
858 try:
860 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
859 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
861 if f.endswith(".rc")])
860 if f.endswith(".rc")])
862 except OSError:
861 except OSError:
863 pass
862 pass
864 return rcs
863 return rcs
865
864
866 def os_rcpath():
865 def os_rcpath():
867 '''return default os-specific hgrc search path'''
866 '''return default os-specific hgrc search path'''
868 path = []
867 path = []
869 # old mod_python does not set sys.argv
868 # old mod_python does not set sys.argv
870 if len(getattr(sys, 'argv', [])) > 0:
869 if len(getattr(sys, 'argv', [])) > 0:
871 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
870 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
872 '/../etc/mercurial'))
871 '/../etc/mercurial'))
873 path.extend(rcfiles('/etc/mercurial'))
872 path.extend(rcfiles('/etc/mercurial'))
874 path.append(os.path.expanduser('~/.hgrc'))
873 path.append(os.path.expanduser('~/.hgrc'))
875 path = [os.path.normpath(f) for f in path]
874 path = [os.path.normpath(f) for f in path]
876 return path
875 return path
877
876
878 def parse_patch_output(output_line):
877 def parse_patch_output(output_line):
879 """parses the output produced by patch and returns the file name"""
878 """parses the output produced by patch and returns the file name"""
880 pf = output_line[14:]
879 pf = output_line[14:]
881 if pf.startswith("'") and pf.endswith("'") and " " in pf:
880 if pf.startswith("'") and pf.endswith("'") and " " in pf:
882 pf = pf[1:-1] # Remove the quotes
881 pf = pf[1:-1] # Remove the quotes
883 return pf
882 return pf
884
883
885 def is_exec(f, last):
884 def is_exec(f, last):
886 """check whether a file is executable"""
885 """check whether a file is executable"""
887 return (os.lstat(f).st_mode & 0100 != 0)
886 return (os.lstat(f).st_mode & 0100 != 0)
888
887
889 def set_exec(f, mode):
888 def set_exec(f, mode):
890 s = os.lstat(f).st_mode
889 s = os.lstat(f).st_mode
891 if (s & 0100 != 0) == mode:
890 if (s & 0100 != 0) == mode:
892 return
891 return
893 if mode:
892 if mode:
894 # Turn on +x for every +r bit when making a file executable
893 # Turn on +x for every +r bit when making a file executable
895 # and obey umask.
894 # and obey umask.
896 umask = os.umask(0)
895 umask = os.umask(0)
897 os.umask(umask)
896 os.umask(umask)
898 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
897 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
899 else:
898 else:
900 os.chmod(f, s & 0666)
899 os.chmod(f, s & 0666)
901
900
902 def set_binary(fd):
901 def set_binary(fd):
903 pass
902 pass
904
903
905 def pconvert(path):
904 def pconvert(path):
906 return path
905 return path
907
906
908 def localpath(path):
907 def localpath(path):
909 return path
908 return path
910
909
911 normpath = os.path.normpath
910 normpath = os.path.normpath
912 samestat = os.path.samestat
911 samestat = os.path.samestat
913
912
914 def makelock(info, pathname):
913 def makelock(info, pathname):
915 try:
914 try:
916 os.symlink(info, pathname)
915 os.symlink(info, pathname)
917 except OSError, why:
916 except OSError, why:
918 if why.errno == errno.EEXIST:
917 if why.errno == errno.EEXIST:
919 raise
918 raise
920 else:
919 else:
921 _makelock_file(info, pathname)
920 _makelock_file(info, pathname)
922
921
923 def readlock(pathname):
922 def readlock(pathname):
924 try:
923 try:
925 return os.readlink(pathname)
924 return os.readlink(pathname)
926 except OSError, why:
925 except OSError, why:
927 if why.errno == errno.EINVAL:
926 if why.errno == errno.EINVAL:
928 return _readlock_file(pathname)
927 return _readlock_file(pathname)
929 else:
928 else:
930 raise
929 raise
931
930
932 def shellquote(s):
931 def shellquote(s):
933 return "'%s'" % s.replace("'", "'\\''")
932 return "'%s'" % s.replace("'", "'\\''")
934
933
935 def testpid(pid):
934 def testpid(pid):
936 '''return False if pid dead, True if running or not sure'''
935 '''return False if pid dead, True if running or not sure'''
937 try:
936 try:
938 os.kill(pid, 0)
937 os.kill(pid, 0)
939 return True
938 return True
940 except OSError, inst:
939 except OSError, inst:
941 return inst.errno != errno.ESRCH
940 return inst.errno != errno.ESRCH
942
941
943 def explain_exit(code):
942 def explain_exit(code):
944 """return a 2-tuple (desc, code) describing a process's status"""
943 """return a 2-tuple (desc, code) describing a process's status"""
945 if os.WIFEXITED(code):
944 if os.WIFEXITED(code):
946 val = os.WEXITSTATUS(code)
945 val = os.WEXITSTATUS(code)
947 return _("exited with status %d") % val, val
946 return _("exited with status %d") % val, val
948 elif os.WIFSIGNALED(code):
947 elif os.WIFSIGNALED(code):
949 val = os.WTERMSIG(code)
948 val = os.WTERMSIG(code)
950 return _("killed by signal %d") % val, val
949 return _("killed by signal %d") % val, val
951 elif os.WIFSTOPPED(code):
950 elif os.WIFSTOPPED(code):
952 val = os.WSTOPSIG(code)
951 val = os.WSTOPSIG(code)
953 return _("stopped by signal %d") % val, val
952 return _("stopped by signal %d") % val, val
954 raise ValueError(_("invalid exit code"))
953 raise ValueError(_("invalid exit code"))
955
954
956 def isowner(fp, st=None):
955 def isowner(fp, st=None):
957 """Return True if the file object f belongs to the current user.
956 """Return True if the file object f belongs to the current user.
958
957
959 The return value of a util.fstat(f) may be passed as the st argument.
958 The return value of a util.fstat(f) may be passed as the st argument.
960 """
959 """
961 if st is None:
960 if st is None:
962 st = fstat(fp)
961 st = fstat(fp)
963 return st.st_uid == os.getuid()
962 return st.st_uid == os.getuid()
964
963
965 def _buildencodefun():
964 def _buildencodefun():
966 e = '_'
965 e = '_'
967 win_reserved = [ord(x) for x in '\\:*?"<>|']
966 win_reserved = [ord(x) for x in '\\:*?"<>|']
968 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
967 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
969 for x in (range(32) + range(126, 256) + win_reserved):
968 for x in (range(32) + range(126, 256) + win_reserved):
970 cmap[chr(x)] = "~%02x" % x
969 cmap[chr(x)] = "~%02x" % x
971 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
970 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
972 cmap[chr(x)] = e + chr(x).lower()
971 cmap[chr(x)] = e + chr(x).lower()
973 dmap = {}
972 dmap = {}
974 for k, v in cmap.iteritems():
973 for k, v in cmap.iteritems():
975 dmap[v] = k
974 dmap[v] = k
976 def decode(s):
975 def decode(s):
977 i = 0
976 i = 0
978 while i < len(s):
977 while i < len(s):
979 for l in xrange(1, 4):
978 for l in xrange(1, 4):
980 try:
979 try:
981 yield dmap[s[i:i+l]]
980 yield dmap[s[i:i+l]]
982 i += l
981 i += l
983 break
982 break
984 except KeyError:
983 except KeyError:
985 pass
984 pass
986 else:
985 else:
987 raise KeyError
986 raise KeyError
988 return (lambda s: "".join([cmap[c] for c in s]),
987 return (lambda s: "".join([cmap[c] for c in s]),
989 lambda s: "".join(list(decode(s))))
988 lambda s: "".join(list(decode(s))))
990
989
991 encodefilename, decodefilename = _buildencodefun()
990 encodefilename, decodefilename = _buildencodefun()
992
991
993 def encodedopener(openerfn, fn):
992 def encodedopener(openerfn, fn):
994 def o(path, *args, **kw):
993 def o(path, *args, **kw):
995 return openerfn(fn(path), *args, **kw)
994 return openerfn(fn(path), *args, **kw)
996 return o
995 return o
997
996
998 def opener(base, audit=True):
997 def opener(base, audit=True):
999 """
998 """
1000 return a function that opens files relative to base
999 return a function that opens files relative to base
1001
1000
1002 this function is used to hide the details of COW semantics and
1001 this function is used to hide the details of COW semantics and
1003 remote file access from higher level code.
1002 remote file access from higher level code.
1004 """
1003 """
1005 p = base
1004 p = base
1006 audit_p = audit
1005 audit_p = audit
1007
1006
1008 def mktempcopy(name):
1007 def mktempcopy(name):
1009 d, fn = os.path.split(name)
1008 d, fn = os.path.split(name)
1010 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1009 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1011 os.close(fd)
1010 os.close(fd)
1012 ofp = posixfile(temp, "wb")
1011 ofp = posixfile(temp, "wb")
1013 try:
1012 try:
1014 try:
1013 try:
1015 ifp = posixfile(name, "rb")
1014 ifp = posixfile(name, "rb")
1016 except IOError, inst:
1015 except IOError, inst:
1017 if not getattr(inst, 'filename', None):
1016 if not getattr(inst, 'filename', None):
1018 inst.filename = name
1017 inst.filename = name
1019 raise
1018 raise
1020 for chunk in filechunkiter(ifp):
1019 for chunk in filechunkiter(ifp):
1021 ofp.write(chunk)
1020 ofp.write(chunk)
1022 ifp.close()
1021 ifp.close()
1023 ofp.close()
1022 ofp.close()
1024 except:
1023 except:
1025 try: os.unlink(temp)
1024 try: os.unlink(temp)
1026 except: pass
1025 except: pass
1027 raise
1026 raise
1028 st = os.lstat(name)
1027 st = os.lstat(name)
1029 os.chmod(temp, st.st_mode)
1028 os.chmod(temp, st.st_mode)
1030 return temp
1029 return temp
1031
1030
1032 class atomictempfile(posixfile):
1031 class atomictempfile(posixfile):
1033 """the file will only be copied when rename is called"""
1032 """the file will only be copied when rename is called"""
1034 def __init__(self, name, mode):
1033 def __init__(self, name, mode):
1035 self.__name = name
1034 self.__name = name
1036 self.temp = mktempcopy(name)
1035 self.temp = mktempcopy(name)
1037 posixfile.__init__(self, self.temp, mode)
1036 posixfile.__init__(self, self.temp, mode)
1038 def rename(self):
1037 def rename(self):
1039 if not self.closed:
1038 if not self.closed:
1040 posixfile.close(self)
1039 posixfile.close(self)
1041 rename(self.temp, localpath(self.__name))
1040 rename(self.temp, localpath(self.__name))
1042 def __del__(self):
1041 def __del__(self):
1043 if not self.closed:
1042 if not self.closed:
1044 try:
1043 try:
1045 os.unlink(self.temp)
1044 os.unlink(self.temp)
1046 except: pass
1045 except: pass
1047 posixfile.close(self)
1046 posixfile.close(self)
1048
1047
1049 class atomicfile(atomictempfile):
1048 class atomicfile(atomictempfile):
1050 """the file will only be copied on close"""
1049 """the file will only be copied on close"""
1051 def __init__(self, name, mode):
1050 def __init__(self, name, mode):
1052 atomictempfile.__init__(self, name, mode)
1051 atomictempfile.__init__(self, name, mode)
1053 def close(self):
1052 def close(self):
1054 self.rename()
1053 self.rename()
1055 def __del__(self):
1054 def __del__(self):
1056 self.rename()
1055 self.rename()
1057
1056
1058 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1057 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1059 if audit_p:
1058 if audit_p:
1060 audit_path(path)
1059 audit_path(path)
1061 f = os.path.join(p, path)
1060 f = os.path.join(p, path)
1062
1061
1063 if not text:
1062 if not text:
1064 mode += "b" # for that other OS
1063 mode += "b" # for that other OS
1065
1064
1066 if mode[0] != "r":
1065 if mode[0] != "r":
1067 try:
1066 try:
1068 nlink = nlinks(f)
1067 nlink = nlinks(f)
1069 except OSError:
1068 except OSError:
1070 d = os.path.dirname(f)
1069 d = os.path.dirname(f)
1071 if not os.path.isdir(d):
1070 if not os.path.isdir(d):
1072 os.makedirs(d)
1071 os.makedirs(d)
1073 else:
1072 else:
1074 if atomic:
1073 if atomic:
1075 return atomicfile(f, mode)
1074 return atomicfile(f, mode)
1076 elif atomictemp:
1075 elif atomictemp:
1077 return atomictempfile(f, mode)
1076 return atomictempfile(f, mode)
1078 if nlink > 1:
1077 if nlink > 1:
1079 rename(mktempcopy(f), f)
1078 rename(mktempcopy(f), f)
1080 return posixfile(f, mode)
1079 return posixfile(f, mode)
1081
1080
1082 return o
1081 return o
1083
1082
1084 class chunkbuffer(object):
1083 class chunkbuffer(object):
1085 """Allow arbitrary sized chunks of data to be efficiently read from an
1084 """Allow arbitrary sized chunks of data to be efficiently read from an
1086 iterator over chunks of arbitrary size."""
1085 iterator over chunks of arbitrary size."""
1087
1086
1088 def __init__(self, in_iter, targetsize = 2**16):
1087 def __init__(self, in_iter, targetsize = 2**16):
1089 """in_iter is the iterator that's iterating over the input chunks.
1088 """in_iter is the iterator that's iterating over the input chunks.
1090 targetsize is how big a buffer to try to maintain."""
1089 targetsize is how big a buffer to try to maintain."""
1091 self.in_iter = iter(in_iter)
1090 self.in_iter = iter(in_iter)
1092 self.buf = ''
1091 self.buf = ''
1093 self.targetsize = int(targetsize)
1092 self.targetsize = int(targetsize)
1094 if self.targetsize <= 0:
1093 if self.targetsize <= 0:
1095 raise ValueError(_("targetsize must be greater than 0, was %d") %
1094 raise ValueError(_("targetsize must be greater than 0, was %d") %
1096 targetsize)
1095 targetsize)
1097 self.iterempty = False
1096 self.iterempty = False
1098
1097
1099 def fillbuf(self):
1098 def fillbuf(self):
1100 """Ignore target size; read every chunk from iterator until empty."""
1099 """Ignore target size; read every chunk from iterator until empty."""
1101 if not self.iterempty:
1100 if not self.iterempty:
1102 collector = cStringIO.StringIO()
1101 collector = cStringIO.StringIO()
1103 collector.write(self.buf)
1102 collector.write(self.buf)
1104 for ch in self.in_iter:
1103 for ch in self.in_iter:
1105 collector.write(ch)
1104 collector.write(ch)
1106 self.buf = collector.getvalue()
1105 self.buf = collector.getvalue()
1107 self.iterempty = True
1106 self.iterempty = True
1108
1107
1109 def read(self, l):
1108 def read(self, l):
1110 """Read L bytes of data from the iterator of chunks of data.
1109 """Read L bytes of data from the iterator of chunks of data.
1111 Returns less than L bytes if the iterator runs dry."""
1110 Returns less than L bytes if the iterator runs dry."""
1112 if l > len(self.buf) and not self.iterempty:
1111 if l > len(self.buf) and not self.iterempty:
1113 # Clamp to a multiple of self.targetsize
1112 # Clamp to a multiple of self.targetsize
1114 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1113 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1115 collector = cStringIO.StringIO()
1114 collector = cStringIO.StringIO()
1116 collector.write(self.buf)
1115 collector.write(self.buf)
1117 collected = len(self.buf)
1116 collected = len(self.buf)
1118 for chunk in self.in_iter:
1117 for chunk in self.in_iter:
1119 collector.write(chunk)
1118 collector.write(chunk)
1120 collected += len(chunk)
1119 collected += len(chunk)
1121 if collected >= targetsize:
1120 if collected >= targetsize:
1122 break
1121 break
1123 if collected < targetsize:
1122 if collected < targetsize:
1124 self.iterempty = True
1123 self.iterempty = True
1125 self.buf = collector.getvalue()
1124 self.buf = collector.getvalue()
1126 s, self.buf = self.buf[:l], buffer(self.buf, l)
1125 s, self.buf = self.buf[:l], buffer(self.buf, l)
1127 return s
1126 return s
1128
1127
1129 def filechunkiter(f, size=65536, limit=None):
1128 def filechunkiter(f, size=65536, limit=None):
1130 """Create a generator that produces the data in the file size
1129 """Create a generator that produces the data in the file size
1131 (default 65536) bytes at a time, up to optional limit (default is
1130 (default 65536) bytes at a time, up to optional limit (default is
1132 to read all data). Chunks may be less than size bytes if the
1131 to read all data). Chunks may be less than size bytes if the
1133 chunk is the last chunk in the file, or the file is a socket or
1132 chunk is the last chunk in the file, or the file is a socket or
1134 some other type of file that sometimes reads less data than is
1133 some other type of file that sometimes reads less data than is
1135 requested."""
1134 requested."""
1136 assert size >= 0
1135 assert size >= 0
1137 assert limit is None or limit >= 0
1136 assert limit is None or limit >= 0
1138 while True:
1137 while True:
1139 if limit is None: nbytes = size
1138 if limit is None: nbytes = size
1140 else: nbytes = min(limit, size)
1139 else: nbytes = min(limit, size)
1141 s = nbytes and f.read(nbytes)
1140 s = nbytes and f.read(nbytes)
1142 if not s: break
1141 if not s: break
1143 if limit: limit -= len(s)
1142 if limit: limit -= len(s)
1144 yield s
1143 yield s
1145
1144
1146 def makedate():
1145 def makedate():
1147 lt = time.localtime()
1146 lt = time.localtime()
1148 if lt[8] == 1 and time.daylight:
1147 if lt[8] == 1 and time.daylight:
1149 tz = time.altzone
1148 tz = time.altzone
1150 else:
1149 else:
1151 tz = time.timezone
1150 tz = time.timezone
1152 return time.mktime(lt), tz
1151 return time.mktime(lt), tz
1153
1152
1154 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1153 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1155 """represent a (unixtime, offset) tuple as a localized time.
1154 """represent a (unixtime, offset) tuple as a localized time.
1156 unixtime is seconds since the epoch, and offset is the time zone's
1155 unixtime is seconds since the epoch, and offset is the time zone's
1157 number of seconds away from UTC. if timezone is false, do not
1156 number of seconds away from UTC. if timezone is false, do not
1158 append time zone to string."""
1157 append time zone to string."""
1159 t, tz = date or makedate()
1158 t, tz = date or makedate()
1160 s = time.strftime(format, time.gmtime(float(t) - tz))
1159 s = time.strftime(format, time.gmtime(float(t) - tz))
1161 if timezone:
1160 if timezone:
1162 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1161 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1163 return s
1162 return s
1164
1163
1165 def strdate(string, format, defaults):
1164 def strdate(string, format, defaults):
1166 """parse a localized time string and return a (unixtime, offset) tuple.
1165 """parse a localized time string and return a (unixtime, offset) tuple.
1167 if the string cannot be parsed, ValueError is raised."""
1166 if the string cannot be parsed, ValueError is raised."""
1168 def timezone(string):
1167 def timezone(string):
1169 tz = string.split()[-1]
1168 tz = string.split()[-1]
1170 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1169 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1171 tz = int(tz)
1170 tz = int(tz)
1172 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1171 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1173 return offset
1172 return offset
1174 if tz == "GMT" or tz == "UTC":
1173 if tz == "GMT" or tz == "UTC":
1175 return 0
1174 return 0
1176 return None
1175 return None
1177
1176
1178 # NOTE: unixtime = localunixtime + offset
1177 # NOTE: unixtime = localunixtime + offset
1179 offset, date = timezone(string), string
1178 offset, date = timezone(string), string
1180 if offset != None:
1179 if offset != None:
1181 date = " ".join(string.split()[:-1])
1180 date = " ".join(string.split()[:-1])
1182
1181
1183 # add missing elements from defaults
1182 # add missing elements from defaults
1184 for part in defaults:
1183 for part in defaults:
1185 found = [True for p in part if ("%"+p) in format]
1184 found = [True for p in part if ("%"+p) in format]
1186 if not found:
1185 if not found:
1187 date += "@" + defaults[part]
1186 date += "@" + defaults[part]
1188 format += "@%" + part[0]
1187 format += "@%" + part[0]
1189
1188
1190 timetuple = time.strptime(date, format)
1189 timetuple = time.strptime(date, format)
1191 localunixtime = int(calendar.timegm(timetuple))
1190 localunixtime = int(calendar.timegm(timetuple))
1192 if offset is None:
1191 if offset is None:
1193 # local timezone
1192 # local timezone
1194 unixtime = int(time.mktime(timetuple))
1193 unixtime = int(time.mktime(timetuple))
1195 offset = unixtime - localunixtime
1194 offset = unixtime - localunixtime
1196 else:
1195 else:
1197 unixtime = localunixtime + offset
1196 unixtime = localunixtime + offset
1198 return unixtime, offset
1197 return unixtime, offset
1199
1198
1200 def parsedate(string, formats=None, defaults=None):
1199 def parsedate(string, formats=None, defaults=None):
1201 """parse a localized time string and return a (unixtime, offset) tuple.
1200 """parse a localized time string and return a (unixtime, offset) tuple.
1202 The date may be a "unixtime offset" string or in one of the specified
1201 The date may be a "unixtime offset" string or in one of the specified
1203 formats."""
1202 formats."""
1204 if not string:
1203 if not string:
1205 return 0, 0
1204 return 0, 0
1206 if not formats:
1205 if not formats:
1207 formats = defaultdateformats
1206 formats = defaultdateformats
1208 string = string.strip()
1207 string = string.strip()
1209 try:
1208 try:
1210 when, offset = map(int, string.split(' '))
1209 when, offset = map(int, string.split(' '))
1211 except ValueError:
1210 except ValueError:
1212 # fill out defaults
1211 # fill out defaults
1213 if not defaults:
1212 if not defaults:
1214 defaults = {}
1213 defaults = {}
1215 now = makedate()
1214 now = makedate()
1216 for part in "d mb yY HI M S".split():
1215 for part in "d mb yY HI M S".split():
1217 if part not in defaults:
1216 if part not in defaults:
1218 if part[0] in "HMS":
1217 if part[0] in "HMS":
1219 defaults[part] = "00"
1218 defaults[part] = "00"
1220 elif part[0] in "dm":
1219 elif part[0] in "dm":
1221 defaults[part] = "1"
1220 defaults[part] = "1"
1222 else:
1221 else:
1223 defaults[part] = datestr(now, "%" + part[0], False)
1222 defaults[part] = datestr(now, "%" + part[0], False)
1224
1223
1225 for format in formats:
1224 for format in formats:
1226 try:
1225 try:
1227 when, offset = strdate(string, format, defaults)
1226 when, offset = strdate(string, format, defaults)
1228 except ValueError:
1227 except ValueError:
1229 pass
1228 pass
1230 else:
1229 else:
1231 break
1230 break
1232 else:
1231 else:
1233 raise Abort(_('invalid date: %r ') % string)
1232 raise Abort(_('invalid date: %r ') % string)
1234 # validate explicit (probably user-specified) date and
1233 # validate explicit (probably user-specified) date and
1235 # time zone offset. values must fit in signed 32 bits for
1234 # time zone offset. values must fit in signed 32 bits for
1236 # current 32-bit linux runtimes. timezones go from UTC-12
1235 # current 32-bit linux runtimes. timezones go from UTC-12
1237 # to UTC+14
1236 # to UTC+14
1238 if abs(when) > 0x7fffffff:
1237 if abs(when) > 0x7fffffff:
1239 raise Abort(_('date exceeds 32 bits: %d') % when)
1238 raise Abort(_('date exceeds 32 bits: %d') % when)
1240 if offset < -50400 or offset > 43200:
1239 if offset < -50400 or offset > 43200:
1241 raise Abort(_('impossible time zone offset: %d') % offset)
1240 raise Abort(_('impossible time zone offset: %d') % offset)
1242 return when, offset
1241 return when, offset
1243
1242
1244 def matchdate(date):
1243 def matchdate(date):
1245 """Return a function that matches a given date match specifier
1244 """Return a function that matches a given date match specifier
1246
1245
1247 Formats include:
1246 Formats include:
1248
1247
1249 '{date}' match a given date to the accuracy provided
1248 '{date}' match a given date to the accuracy provided
1250
1249
1251 '<{date}' on or before a given date
1250 '<{date}' on or before a given date
1252
1251
1253 '>{date}' on or after a given date
1252 '>{date}' on or after a given date
1254
1253
1255 """
1254 """
1256
1255
1257 def lower(date):
1256 def lower(date):
1258 return parsedate(date, extendeddateformats)[0]
1257 return parsedate(date, extendeddateformats)[0]
1259
1258
1260 def upper(date):
1259 def upper(date):
1261 d = dict(mb="12", HI="23", M="59", S="59")
1260 d = dict(mb="12", HI="23", M="59", S="59")
1262 for days in "31 30 29".split():
1261 for days in "31 30 29".split():
1263 try:
1262 try:
1264 d["d"] = days
1263 d["d"] = days
1265 return parsedate(date, extendeddateformats, d)[0]
1264 return parsedate(date, extendeddateformats, d)[0]
1266 except:
1265 except:
1267 pass
1266 pass
1268 d["d"] = "28"
1267 d["d"] = "28"
1269 return parsedate(date, extendeddateformats, d)[0]
1268 return parsedate(date, extendeddateformats, d)[0]
1270
1269
1271 if date[0] == "<":
1270 if date[0] == "<":
1272 when = upper(date[1:])
1271 when = upper(date[1:])
1273 return lambda x: x <= when
1272 return lambda x: x <= when
1274 elif date[0] == ">":
1273 elif date[0] == ">":
1275 when = lower(date[1:])
1274 when = lower(date[1:])
1276 return lambda x: x >= when
1275 return lambda x: x >= when
1277 elif date[0] == "-":
1276 elif date[0] == "-":
1278 try:
1277 try:
1279 days = int(date[1:])
1278 days = int(date[1:])
1280 except ValueError:
1279 except ValueError:
1281 raise Abort(_("invalid day spec: %s") % date[1:])
1280 raise Abort(_("invalid day spec: %s") % date[1:])
1282 when = makedate()[0] - days * 3600 * 24
1281 when = makedate()[0] - days * 3600 * 24
1283 return lambda x: x >= when
1282 return lambda x: x >= when
1284 elif " to " in date:
1283 elif " to " in date:
1285 a, b = date.split(" to ")
1284 a, b = date.split(" to ")
1286 start, stop = lower(a), upper(b)
1285 start, stop = lower(a), upper(b)
1287 return lambda x: x >= start and x <= stop
1286 return lambda x: x >= start and x <= stop
1288 else:
1287 else:
1289 start, stop = lower(date), upper(date)
1288 start, stop = lower(date), upper(date)
1290 return lambda x: x >= start and x <= stop
1289 return lambda x: x >= start and x <= stop
1291
1290
1292 def shortuser(user):
1291 def shortuser(user):
1293 """Return a short representation of a user name or email address."""
1292 """Return a short representation of a user name or email address."""
1294 f = user.find('@')
1293 f = user.find('@')
1295 if f >= 0:
1294 if f >= 0:
1296 user = user[:f]
1295 user = user[:f]
1297 f = user.find('<')
1296 f = user.find('<')
1298 if f >= 0:
1297 if f >= 0:
1299 user = user[f+1:]
1298 user = user[f+1:]
1300 f = user.find(' ')
1299 f = user.find(' ')
1301 if f >= 0:
1300 if f >= 0:
1302 user = user[:f]
1301 user = user[:f]
1303 f = user.find('.')
1302 f = user.find('.')
1304 if f >= 0:
1303 if f >= 0:
1305 user = user[:f]
1304 user = user[:f]
1306 return user
1305 return user
1307
1306
1308 def ellipsis(text, maxlength=400):
1307 def ellipsis(text, maxlength=400):
1309 """Trim string to at most maxlength (default: 400) characters."""
1308 """Trim string to at most maxlength (default: 400) characters."""
1310 if len(text) <= maxlength:
1309 if len(text) <= maxlength:
1311 return text
1310 return text
1312 else:
1311 else:
1313 return "%s..." % (text[:maxlength-3])
1312 return "%s..." % (text[:maxlength-3])
1314
1313
1315 def walkrepos(path):
1314 def walkrepos(path):
1316 '''yield every hg repository under path, recursively.'''
1315 '''yield every hg repository under path, recursively.'''
1317 def errhandler(err):
1316 def errhandler(err):
1318 if err.filename == path:
1317 if err.filename == path:
1319 raise err
1318 raise err
1320
1319
1321 for root, dirs, files in os.walk(path, onerror=errhandler):
1320 for root, dirs, files in os.walk(path, onerror=errhandler):
1322 for d in dirs:
1321 for d in dirs:
1323 if d == '.hg':
1322 if d == '.hg':
1324 yield root
1323 yield root
1325 dirs[:] = []
1324 dirs[:] = []
1326 break
1325 break
1327
1326
1328 _rcpath = None
1327 _rcpath = None
1329
1328
1330 def rcpath():
1329 def rcpath():
1331 '''return hgrc search path. if env var HGRCPATH is set, use it.
1330 '''return hgrc search path. if env var HGRCPATH is set, use it.
1332 for each item in path, if directory, use files ending in .rc,
1331 for each item in path, if directory, use files ending in .rc,
1333 else use item.
1332 else use item.
1334 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1333 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1335 if no HGRCPATH, use default os-specific path.'''
1334 if no HGRCPATH, use default os-specific path.'''
1336 global _rcpath
1335 global _rcpath
1337 if _rcpath is None:
1336 if _rcpath is None:
1338 if 'HGRCPATH' in os.environ:
1337 if 'HGRCPATH' in os.environ:
1339 _rcpath = []
1338 _rcpath = []
1340 for p in os.environ['HGRCPATH'].split(os.pathsep):
1339 for p in os.environ['HGRCPATH'].split(os.pathsep):
1341 if not p: continue
1340 if not p: continue
1342 if os.path.isdir(p):
1341 if os.path.isdir(p):
1343 for f in os.listdir(p):
1342 for f in os.listdir(p):
1344 if f.endswith('.rc'):
1343 if f.endswith('.rc'):
1345 _rcpath.append(os.path.join(p, f))
1344 _rcpath.append(os.path.join(p, f))
1346 else:
1345 else:
1347 _rcpath.append(p)
1346 _rcpath.append(p)
1348 else:
1347 else:
1349 _rcpath = os_rcpath()
1348 _rcpath = os_rcpath()
1350 return _rcpath
1349 return _rcpath
1351
1350
1352 def bytecount(nbytes):
1351 def bytecount(nbytes):
1353 '''return byte count formatted as readable string, with units'''
1352 '''return byte count formatted as readable string, with units'''
1354
1353
1355 units = (
1354 units = (
1356 (100, 1<<30, _('%.0f GB')),
1355 (100, 1<<30, _('%.0f GB')),
1357 (10, 1<<30, _('%.1f GB')),
1356 (10, 1<<30, _('%.1f GB')),
1358 (1, 1<<30, _('%.2f GB')),
1357 (1, 1<<30, _('%.2f GB')),
1359 (100, 1<<20, _('%.0f MB')),
1358 (100, 1<<20, _('%.0f MB')),
1360 (10, 1<<20, _('%.1f MB')),
1359 (10, 1<<20, _('%.1f MB')),
1361 (1, 1<<20, _('%.2f MB')),
1360 (1, 1<<20, _('%.2f MB')),
1362 (100, 1<<10, _('%.0f KB')),
1361 (100, 1<<10, _('%.0f KB')),
1363 (10, 1<<10, _('%.1f KB')),
1362 (10, 1<<10, _('%.1f KB')),
1364 (1, 1<<10, _('%.2f KB')),
1363 (1, 1<<10, _('%.2f KB')),
1365 (1, 1, _('%.0f bytes')),
1364 (1, 1, _('%.0f bytes')),
1366 )
1365 )
1367
1366
1368 for multiplier, divisor, format in units:
1367 for multiplier, divisor, format in units:
1369 if nbytes >= divisor * multiplier:
1368 if nbytes >= divisor * multiplier:
1370 return format % (nbytes / float(divisor))
1369 return format % (nbytes / float(divisor))
1371 return units[-1][2] % nbytes
1370 return units[-1][2] % nbytes
1372
1371
1373 def drop_scheme(scheme, path):
1372 def drop_scheme(scheme, path):
1374 sc = scheme + ':'
1373 sc = scheme + ':'
1375 if path.startswith(sc):
1374 if path.startswith(sc):
1376 path = path[len(sc):]
1375 path = path[len(sc):]
1377 if path.startswith('//'):
1376 if path.startswith('//'):
1378 path = path[2:]
1377 path = path[2:]
1379 return path
1378 return path
@@ -1,278 +1,278 b''
1 adding beans/black
1 adding beans/black
2 adding beans/borlotti
2 adding beans/borlotti
3 adding beans/kidney
3 adding beans/kidney
4 adding beans/navy
4 adding beans/navy
5 adding beans/pinto
5 adding beans/pinto
6 adding beans/turtle
6 adding beans/turtle
7 adding fennel
7 adding fennel
8 adding fenugreek
8 adding fenugreek
9 adding fiddlehead
9 adding fiddlehead
10 adding glob:glob
10 adding glob:glob
11 adding mammals/Procyonidae/cacomistle
11 adding mammals/Procyonidae/cacomistle
12 adding mammals/Procyonidae/coatimundi
12 adding mammals/Procyonidae/coatimundi
13 adding mammals/Procyonidae/raccoon
13 adding mammals/Procyonidae/raccoon
14 adding mammals/skunk
14 adding mammals/skunk
15 hg debugwalk
15 hg debugwalk
16 f beans/black beans/black
16 f beans/black beans/black
17 f beans/borlotti beans/borlotti
17 f beans/borlotti beans/borlotti
18 f beans/kidney beans/kidney
18 f beans/kidney beans/kidney
19 f beans/navy beans/navy
19 f beans/navy beans/navy
20 f beans/pinto beans/pinto
20 f beans/pinto beans/pinto
21 f beans/turtle beans/turtle
21 f beans/turtle beans/turtle
22 f fennel fennel
22 f fennel fennel
23 f fenugreek fenugreek
23 f fenugreek fenugreek
24 f fiddlehead fiddlehead
24 f fiddlehead fiddlehead
25 f glob:glob glob:glob
25 f glob:glob glob:glob
26 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
26 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
27 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
27 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
28 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
28 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
29 f mammals/skunk mammals/skunk
29 f mammals/skunk mammals/skunk
30
30
31 hg debugwalk -I.
31 hg debugwalk -I.
32 f beans/black beans/black
32 f beans/black beans/black
33 f beans/borlotti beans/borlotti
33 f beans/borlotti beans/borlotti
34 f beans/kidney beans/kidney
34 f beans/kidney beans/kidney
35 f beans/navy beans/navy
35 f beans/navy beans/navy
36 f beans/pinto beans/pinto
36 f beans/pinto beans/pinto
37 f beans/turtle beans/turtle
37 f beans/turtle beans/turtle
38 f fennel fennel
38 f fennel fennel
39 f fenugreek fenugreek
39 f fenugreek fenugreek
40 f fiddlehead fiddlehead
40 f fiddlehead fiddlehead
41 f glob:glob glob:glob
41 f glob:glob glob:glob
42 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
42 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
43 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
43 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
44 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
44 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
45 f mammals/skunk mammals/skunk
45 f mammals/skunk mammals/skunk
46
46
47 cd mammals
47 cd mammals
48
48
49 hg debugwalk
49 hg debugwalk
50 f beans/black ../beans/black
50 f beans/black ../beans/black
51 f beans/borlotti ../beans/borlotti
51 f beans/borlotti ../beans/borlotti
52 f beans/kidney ../beans/kidney
52 f beans/kidney ../beans/kidney
53 f beans/navy ../beans/navy
53 f beans/navy ../beans/navy
54 f beans/pinto ../beans/pinto
54 f beans/pinto ../beans/pinto
55 f beans/turtle ../beans/turtle
55 f beans/turtle ../beans/turtle
56 f fennel ../fennel
56 f fennel ../fennel
57 f fenugreek ../fenugreek
57 f fenugreek ../fenugreek
58 f fiddlehead ../fiddlehead
58 f fiddlehead ../fiddlehead
59 f glob:glob ../glob:glob
59 f glob:glob ../glob:glob
60 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
60 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
61 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
61 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
62 f mammals/Procyonidae/raccoon Procyonidae/raccoon
62 f mammals/Procyonidae/raccoon Procyonidae/raccoon
63 f mammals/skunk skunk
63 f mammals/skunk skunk
64
64
65 hg debugwalk -X ../beans
65 hg debugwalk -X ../beans
66 f fennel ../fennel
66 f fennel ../fennel
67 f fenugreek ../fenugreek
67 f fenugreek ../fenugreek
68 f fiddlehead ../fiddlehead
68 f fiddlehead ../fiddlehead
69 f glob:glob ../glob:glob
69 f glob:glob ../glob:glob
70 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
70 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
71 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
71 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
72 f mammals/Procyonidae/raccoon Procyonidae/raccoon
72 f mammals/Procyonidae/raccoon Procyonidae/raccoon
73 f mammals/skunk skunk
73 f mammals/skunk skunk
74
74
75 hg debugwalk -I *k
75 hg debugwalk -I *k
76 m mammals/skunk skunk
76 f mammals/skunk skunk
77
77
78 hg debugwalk -I glob:*k
78 hg debugwalk -I glob:*k
79 m mammals/skunk skunk
79 f mammals/skunk skunk
80
80
81 hg debugwalk -I relglob:*k
81 hg debugwalk -I relglob:*k
82 f beans/black ../beans/black
82 f fenugreek ../fenugreek
83 f fenugreek ../fenugreek
83 m beans/black ../beans/black
84 f mammals/skunk skunk
84 m mammals/skunk skunk
85
85
86 hg debugwalk -I relglob:*k .
86 hg debugwalk -I relglob:*k .
87 f mammals/skunk skunk
87 f mammals/skunk skunk
88
88
89 hg debugwalk -I re:.*k$
89 hg debugwalk -I re:.*k$
90 f beans/black ../beans/black
90 f fenugreek ../fenugreek
91 f fenugreek ../fenugreek
91 m beans/black ../beans/black
92 f mammals/skunk skunk
92 m mammals/skunk skunk
93
93
94 hg debugwalk -I relre:.*k$
94 hg debugwalk -I relre:.*k$
95 f beans/black ../beans/black
95 f fenugreek ../fenugreek
96 f fenugreek ../fenugreek
96 m beans/black ../beans/black
97 f mammals/skunk skunk
97 m mammals/skunk skunk
98
98
99 hg debugwalk -I path:beans
99 hg debugwalk -I path:beans
100 f beans/black ../beans/black
100 f beans/black ../beans/black
101 f beans/borlotti ../beans/borlotti
101 f beans/borlotti ../beans/borlotti
102 f beans/kidney ../beans/kidney
102 f beans/kidney ../beans/kidney
103 f beans/navy ../beans/navy
103 f beans/navy ../beans/navy
104 f beans/pinto ../beans/pinto
104 f beans/pinto ../beans/pinto
105 f beans/turtle ../beans/turtle
105 f beans/turtle ../beans/turtle
106
106
107 hg debugwalk -I relpath:../beans
107 hg debugwalk -I relpath:../beans
108 f beans/black ../beans/black
108 f beans/black ../beans/black
109 f beans/borlotti ../beans/borlotti
109 f beans/borlotti ../beans/borlotti
110 f beans/kidney ../beans/kidney
110 f beans/kidney ../beans/kidney
111 f beans/navy ../beans/navy
111 f beans/navy ../beans/navy
112 f beans/pinto ../beans/pinto
112 f beans/pinto ../beans/pinto
113 f beans/turtle ../beans/turtle
113 f beans/turtle ../beans/turtle
114
114
115 hg debugwalk .
115 hg debugwalk .
116 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
116 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
117 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
117 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
118 f mammals/Procyonidae/raccoon Procyonidae/raccoon
118 f mammals/Procyonidae/raccoon Procyonidae/raccoon
119 f mammals/skunk skunk
119 f mammals/skunk skunk
120
120
121 hg debugwalk -I.
121 hg debugwalk -I.
122 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
122 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
123 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
123 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
124 f mammals/Procyonidae/raccoon Procyonidae/raccoon
124 f mammals/Procyonidae/raccoon Procyonidae/raccoon
125 f mammals/skunk skunk
125 f mammals/skunk skunk
126
126
127 hg debugwalk Procyonidae
127 hg debugwalk Procyonidae
128 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
128 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
129 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
129 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
130 f mammals/Procyonidae/raccoon Procyonidae/raccoon
130 f mammals/Procyonidae/raccoon Procyonidae/raccoon
131
131
132 cd Procyonidae
132 cd Procyonidae
133
133
134 hg debugwalk .
134 hg debugwalk .
135 f mammals/Procyonidae/cacomistle cacomistle
135 f mammals/Procyonidae/cacomistle cacomistle
136 f mammals/Procyonidae/coatimundi coatimundi
136 f mammals/Procyonidae/coatimundi coatimundi
137 f mammals/Procyonidae/raccoon raccoon
137 f mammals/Procyonidae/raccoon raccoon
138
138
139 hg debugwalk ..
139 hg debugwalk ..
140 f mammals/Procyonidae/cacomistle cacomistle
140 f mammals/Procyonidae/cacomistle cacomistle
141 f mammals/Procyonidae/coatimundi coatimundi
141 f mammals/Procyonidae/coatimundi coatimundi
142 f mammals/Procyonidae/raccoon raccoon
142 f mammals/Procyonidae/raccoon raccoon
143 f mammals/skunk ../skunk
143 f mammals/skunk ../skunk
144
144
145 cd ..
145 cd ..
146
146
147 hg debugwalk ../beans
147 hg debugwalk ../beans
148 f beans/black ../beans/black
148 f beans/black ../beans/black
149 f beans/borlotti ../beans/borlotti
149 f beans/borlotti ../beans/borlotti
150 f beans/kidney ../beans/kidney
150 f beans/kidney ../beans/kidney
151 f beans/navy ../beans/navy
151 f beans/navy ../beans/navy
152 f beans/pinto ../beans/pinto
152 f beans/pinto ../beans/pinto
153 f beans/turtle ../beans/turtle
153 f beans/turtle ../beans/turtle
154
154
155 hg debugwalk .
155 hg debugwalk .
156 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
156 f mammals/Procyonidae/cacomistle Procyonidae/cacomistle
157 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
157 f mammals/Procyonidae/coatimundi Procyonidae/coatimundi
158 f mammals/Procyonidae/raccoon Procyonidae/raccoon
158 f mammals/Procyonidae/raccoon Procyonidae/raccoon
159 f mammals/skunk skunk
159 f mammals/skunk skunk
160
160
161 hg debugwalk .hg
161 hg debugwalk .hg
162 .hg: No such file or directory
162 .hg: No such file or directory
163
163
164 hg debugwalk ../.hg
164 hg debugwalk ../.hg
165 abort: path contains illegal component: .hg
165 abort: path contains illegal component: .hg
166
166
167
167
168 cd ..
168 cd ..
169
169
170 hg debugwalk -Ibeans
170 hg debugwalk -Ibeans
171 f beans/black beans/black
171 f beans/black beans/black
172 f beans/borlotti beans/borlotti
172 f beans/borlotti beans/borlotti
173 f beans/kidney beans/kidney
173 f beans/kidney beans/kidney
174 f beans/navy beans/navy
174 f beans/navy beans/navy
175 f beans/pinto beans/pinto
175 f beans/pinto beans/pinto
176 f beans/turtle beans/turtle
176 f beans/turtle beans/turtle
177
177
178 hg debugwalk glob:mammals/../beans/b*
178 hg debugwalk glob:mammals/../beans/b*
179 f beans/black beans/black
179 f beans/black beans/black
180 f beans/borlotti beans/borlotti
180 f beans/borlotti beans/borlotti
181
181
182 hg debugwalk -X*/Procyonidae mammals
182 hg debugwalk -X*/Procyonidae mammals
183 f mammals/skunk mammals/skunk
183 f mammals/skunk mammals/skunk
184
184
185 hg debugwalk path:mammals
185 hg debugwalk path:mammals
186 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
186 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
187 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
187 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
188 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
188 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
189 f mammals/skunk mammals/skunk
189 f mammals/skunk mammals/skunk
190
190
191 hg debugwalk ..
191 hg debugwalk ..
192 abort: .. not under root
192 abort: .. not under root
193
193
194 hg debugwalk beans/../..
194 hg debugwalk beans/../..
195 abort: beans/../.. not under root
195 abort: beans/../.. not under root
196
196
197 hg debugwalk .hg
197 hg debugwalk .hg
198 abort: path contains illegal component: .hg
198 abort: path contains illegal component: .hg
199
199
200
200
201 hg debugwalk beans/../.hg
201 hg debugwalk beans/../.hg
202 abort: path contains illegal component: .hg
202 abort: path contains illegal component: .hg
203
203
204
204
205 hg debugwalk beans/../.hg/data
205 hg debugwalk beans/../.hg/data
206 abort: path contains illegal component: .hg/data
206 abort: path contains illegal component: .hg/data
207
207
208
208
209 hg debugwalk beans/.hg
209 hg debugwalk beans/.hg
210 beans/.hg: No such file or directory
210 beans/.hg: No such file or directory
211
211
212 hg debugwalk glob:*
212 hg debugwalk glob:*
213 f fennel fennel
213 f fennel fennel
214 f fenugreek fenugreek
214 f fenugreek fenugreek
215 f fiddlehead fiddlehead
215 f fiddlehead fiddlehead
216 f glob:glob glob:glob
216 f glob:glob glob:glob
217
217
218 hg debugwalk re:.*[kb]$
218 hg debugwalk re:.*[kb]$
219 f beans/black beans/black
219 f beans/black beans/black
220 f fenugreek fenugreek
220 f fenugreek fenugreek
221 f glob:glob glob:glob
221 f glob:glob glob:glob
222 f mammals/skunk mammals/skunk
222 f mammals/skunk mammals/skunk
223
223
224 hg debugwalk path:beans/black
224 hg debugwalk path:beans/black
225 f beans/black beans/black exact
225 f beans/black beans/black exact
226
226
227 hg debugwalk path:beans//black
227 hg debugwalk path:beans//black
228 f beans/black beans/black exact
228 f beans/black beans/black exact
229
229
230 hg debugwalk relglob:Procyonidae
230 hg debugwalk relglob:Procyonidae
231 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
231 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
232 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
232 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
233 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
233 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
234
234
235 hg debugwalk relglob:Procyonidae/ fennel
235 hg debugwalk relglob:Procyonidae/ fennel
236 f fennel fennel exact
236 f fennel fennel exact
237 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
237 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
238 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
238 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
239 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
239 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
240
240
241 hg debugwalk beans glob:beans/*
241 hg debugwalk beans glob:beans/*
242 f beans/black beans/black
242 f beans/black beans/black
243 f beans/borlotti beans/borlotti
243 f beans/borlotti beans/borlotti
244 f beans/kidney beans/kidney
244 f beans/kidney beans/kidney
245 f beans/navy beans/navy
245 f beans/navy beans/navy
246 f beans/pinto beans/pinto
246 f beans/pinto beans/pinto
247 f beans/turtle beans/turtle
247 f beans/turtle beans/turtle
248
248
249 hg debugwalk glob:mamm**
249 hg debugwalk glob:mamm**
250 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
250 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
251 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
251 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
252 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
252 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
253 f mammals/skunk mammals/skunk
253 f mammals/skunk mammals/skunk
254
254
255 hg debugwalk glob:mamm** fennel
255 hg debugwalk glob:mamm** fennel
256 f fennel fennel exact
256 f fennel fennel exact
257 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
257 f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle
258 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
258 f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi
259 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
259 f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon
260 f mammals/skunk mammals/skunk
260 f mammals/skunk mammals/skunk
261
261
262 hg debugwalk glob:j*
262 hg debugwalk glob:j*
263
263
264 hg debugwalk NOEXIST
264 hg debugwalk NOEXIST
265 NOEXIST: No such file or directory
265 NOEXIST: No such file or directory
266
266
267 hg debugwalk fifo
267 hg debugwalk fifo
268 fifo: unsupported file type (type is fifo)
268 fifo: unsupported file type (type is fifo)
269
269
270 hg debugwalk fenugreek
270 hg debugwalk fenugreek
271 m fenugreek fenugreek exact
271 m fenugreek fenugreek exact
272
272
273 hg debugwalk fenugreek
273 hg debugwalk fenugreek
274 m fenugreek fenugreek exact
274 m fenugreek fenugreek exact
275
275
276 hg debugwalk new
276 hg debugwalk new
277 f new new exact
277 f new new exact
278
278
General Comments 0
You need to be logged in to leave comments. Login now