##// END OF EJS Templates
Merge with crew-stable.
Patrick Mezard -
r4446:30e7aa75 merge default
parent child Browse files
Show More
@@ -1,590 +1,592 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 _
11 from i18n import _
12 import struct, os, time, bisect, stat, strutil, util, re, errno
12 import struct, os, time, bisect, stat, strutil, util, re, errno
13 import cStringIO
13 import cStringIO
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.fp = None
24 self.fp = None
25 self.pl = None
25 self.pl = None
26 self.dirs = None
26 self.dirs = None
27 self.copymap = {}
27 self.copymap = {}
28 self.ignorefunc = None
28 self.ignorefunc = None
29 self._branch = None
29 self._branch = None
30
30
31 def wjoin(self, f):
31 def wjoin(self, f):
32 return os.path.join(self.root, f)
32 return os.path.join(self.root, f)
33
33
34 def getcwd(self):
34 def getcwd(self):
35 cwd = os.getcwd()
35 cwd = os.getcwd()
36 if cwd == self.root: return ''
36 if cwd == self.root: return ''
37 # self.root ends with a path separator if self.root is '/' or 'C:\'
37 # self.root ends with a path separator if self.root is '/' or 'C:\'
38 rootsep = self.root
38 rootsep = self.root
39 if not rootsep.endswith(os.sep):
39 if not rootsep.endswith(os.sep):
40 rootsep += os.sep
40 rootsep += os.sep
41 if cwd.startswith(rootsep):
41 if cwd.startswith(rootsep):
42 return cwd[len(rootsep):]
42 return cwd[len(rootsep):]
43 else:
43 else:
44 # we're outside the repo. return an absolute path.
44 # we're outside the repo. return an absolute path.
45 return cwd
45 return cwd
46
46
47 def hgignore(self):
47 def hgignore(self):
48 '''return the contents of .hgignore files as a list of patterns.
48 '''return the contents of .hgignore files as a list of patterns.
49
49
50 the files parsed for patterns include:
50 the files parsed for patterns include:
51 .hgignore in the repository root
51 .hgignore in the repository root
52 any additional files specified in the [ui] section of ~/.hgrc
52 any additional files specified in the [ui] section of ~/.hgrc
53
53
54 trailing white space is dropped.
54 trailing white space is dropped.
55 the escape character is backslash.
55 the escape character is backslash.
56 comments start with #.
56 comments start with #.
57 empty lines are skipped.
57 empty lines are skipped.
58
58
59 lines can be of the following formats:
59 lines can be of the following formats:
60
60
61 syntax: regexp # defaults following lines to non-rooted regexps
61 syntax: regexp # defaults following lines to non-rooted regexps
62 syntax: glob # defaults following lines to non-rooted globs
62 syntax: glob # defaults following lines to non-rooted globs
63 re:pattern # non-rooted regular expression
63 re:pattern # non-rooted regular expression
64 glob:pattern # non-rooted glob
64 glob:pattern # non-rooted glob
65 pattern # pattern of the current default type'''
65 pattern # pattern of the current default type'''
66 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
66 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
67 def parselines(fp):
67 def parselines(fp):
68 for line in fp:
68 for line in fp:
69 if not line.endswith('\n'):
70 line += '\n'
69 escape = False
71 escape = False
70 for i in xrange(len(line)):
72 for i in xrange(len(line)):
71 if escape: escape = False
73 if escape: escape = False
72 elif line[i] == '\\': escape = True
74 elif line[i] == '\\': escape = True
73 elif line[i] == '#': break
75 elif line[i] == '#': break
74 line = line[:i].rstrip()
76 line = line[:i].rstrip()
75 if line: yield line
77 if line: yield line
76 repoignore = self.wjoin('.hgignore')
78 repoignore = self.wjoin('.hgignore')
77 files = [repoignore]
79 files = [repoignore]
78 files.extend(self.ui.hgignorefiles())
80 files.extend(self.ui.hgignorefiles())
79 pats = {}
81 pats = {}
80 for f in files:
82 for f in files:
81 try:
83 try:
82 pats[f] = []
84 pats[f] = []
83 fp = open(f)
85 fp = open(f)
84 syntax = 'relre:'
86 syntax = 'relre:'
85 for line in parselines(fp):
87 for line in parselines(fp):
86 if line.startswith('syntax:'):
88 if line.startswith('syntax:'):
87 s = line[7:].strip()
89 s = line[7:].strip()
88 try:
90 try:
89 syntax = syntaxes[s]
91 syntax = syntaxes[s]
90 except KeyError:
92 except KeyError:
91 self.ui.warn(_("%s: ignoring invalid "
93 self.ui.warn(_("%s: ignoring invalid "
92 "syntax '%s'\n") % (f, s))
94 "syntax '%s'\n") % (f, s))
93 continue
95 continue
94 pat = syntax + line
96 pat = syntax + line
95 for s in syntaxes.values():
97 for s in syntaxes.values():
96 if line.startswith(s):
98 if line.startswith(s):
97 pat = line
99 pat = line
98 break
100 break
99 pats[f].append(pat)
101 pats[f].append(pat)
100 except IOError, inst:
102 except IOError, inst:
101 if f != repoignore:
103 if f != repoignore:
102 self.ui.warn(_("skipping unreadable ignore file"
104 self.ui.warn(_("skipping unreadable ignore file"
103 " '%s': %s\n") % (f, inst.strerror))
105 " '%s': %s\n") % (f, inst.strerror))
104 return pats
106 return pats
105
107
106 def ignore(self, fn):
108 def ignore(self, fn):
107 '''default match function used by dirstate and
109 '''default match function used by dirstate and
108 localrepository. this honours the repository .hgignore file
110 localrepository. this honours the repository .hgignore file
109 and any other files specified in the [ui] section of .hgrc.'''
111 and any other files specified in the [ui] section of .hgrc.'''
110 if not self.ignorefunc:
112 if not self.ignorefunc:
111 ignore = self.hgignore()
113 ignore = self.hgignore()
112 allpats = []
114 allpats = []
113 [allpats.extend(patlist) for patlist in ignore.values()]
115 [allpats.extend(patlist) for patlist in ignore.values()]
114 if allpats:
116 if allpats:
115 try:
117 try:
116 files, self.ignorefunc, anypats = (
118 files, self.ignorefunc, anypats = (
117 util.matcher(self.root, inc=allpats, src='.hgignore'))
119 util.matcher(self.root, inc=allpats, src='.hgignore'))
118 except util.Abort:
120 except util.Abort:
119 # Re-raise an exception where the src is the right file
121 # Re-raise an exception where the src is the right file
120 for f, patlist in ignore.items():
122 for f, patlist in ignore.items():
121 files, self.ignorefunc, anypats = (
123 files, self.ignorefunc, anypats = (
122 util.matcher(self.root, inc=patlist, src=f))
124 util.matcher(self.root, inc=patlist, src=f))
123 else:
125 else:
124 self.ignorefunc = util.never
126 self.ignorefunc = util.never
125 return self.ignorefunc(fn)
127 return self.ignorefunc(fn)
126
128
127 def __del__(self):
129 def __del__(self):
128 if self.dirty:
130 if self.dirty:
129 self.write()
131 self.write()
130
132
131 def __getitem__(self, key):
133 def __getitem__(self, key):
132 try:
134 try:
133 return self.map[key]
135 return self.map[key]
134 except TypeError:
136 except TypeError:
135 self.lazyread()
137 self.lazyread()
136 return self[key]
138 return self[key]
137
139
138 _unknown = ('?', 0, 0, 0)
140 _unknown = ('?', 0, 0, 0)
139
141
140 def get(self, key):
142 def get(self, key):
141 try:
143 try:
142 return self[key]
144 return self[key]
143 except KeyError:
145 except KeyError:
144 return self._unknown
146 return self._unknown
145
147
146 def __contains__(self, key):
148 def __contains__(self, key):
147 self.lazyread()
149 self.lazyread()
148 return key in self.map
150 return key in self.map
149
151
150 def parents(self):
152 def parents(self):
151 if self.pl is None:
153 if self.pl is None:
152 self.pl = [nullid, nullid]
154 self.pl = [nullid, nullid]
153 try:
155 try:
154 self.fp = self.opener('dirstate')
156 self.fp = self.opener('dirstate')
155 st = self.fp.read(40)
157 st = self.fp.read(40)
156 if len(st) == 40:
158 if len(st) == 40:
157 self.pl = st[:20], st[20:40]
159 self.pl = st[:20], st[20:40]
158 except IOError, err:
160 except IOError, err:
159 if err.errno != errno.ENOENT: raise
161 if err.errno != errno.ENOENT: raise
160 return self.pl
162 return self.pl
161
163
162 def branch(self):
164 def branch(self):
163 if not self._branch:
165 if not self._branch:
164 try:
166 try:
165 self._branch = self.opener("branch").read().strip()\
167 self._branch = self.opener("branch").read().strip()\
166 or "default"
168 or "default"
167 except IOError:
169 except IOError:
168 self._branch = "default"
170 self._branch = "default"
169 return self._branch
171 return self._branch
170
172
171 def markdirty(self):
173 def markdirty(self):
172 if not self.dirty:
174 if not self.dirty:
173 self.dirty = 1
175 self.dirty = 1
174
176
175 def setparents(self, p1, p2=nullid):
177 def setparents(self, p1, p2=nullid):
176 self.lazyread()
178 self.lazyread()
177 self.markdirty()
179 self.markdirty()
178 self.pl = p1, p2
180 self.pl = p1, p2
179
181
180 def setbranch(self, branch):
182 def setbranch(self, branch):
181 self._branch = branch
183 self._branch = branch
182 self.opener("branch", "w").write(branch + '\n')
184 self.opener("branch", "w").write(branch + '\n')
183
185
184 def state(self, key):
186 def state(self, key):
185 try:
187 try:
186 return self[key][0]
188 return self[key][0]
187 except KeyError:
189 except KeyError:
188 return "?"
190 return "?"
189
191
190 def lazyread(self):
192 def lazyread(self):
191 if self.map is None:
193 if self.map is None:
192 self.read()
194 self.read()
193
195
194 def parse(self, st):
196 def parse(self, st):
195 self.pl = [st[:20], st[20: 40]]
197 self.pl = [st[:20], st[20: 40]]
196
198
197 # deref fields so they will be local in loop
199 # deref fields so they will be local in loop
198 map = self.map
200 map = self.map
199 copymap = self.copymap
201 copymap = self.copymap
200 format = self.format
202 format = self.format
201 unpack = struct.unpack
203 unpack = struct.unpack
202
204
203 pos = 40
205 pos = 40
204 e_size = struct.calcsize(format)
206 e_size = struct.calcsize(format)
205
207
206 while pos < len(st):
208 while pos < len(st):
207 newpos = pos + e_size
209 newpos = pos + e_size
208 e = unpack(format, st[pos:newpos])
210 e = unpack(format, st[pos:newpos])
209 l = e[4]
211 l = e[4]
210 pos = newpos
212 pos = newpos
211 newpos = pos + l
213 newpos = pos + l
212 f = st[pos:newpos]
214 f = st[pos:newpos]
213 if '\0' in f:
215 if '\0' in f:
214 f, c = f.split('\0')
216 f, c = f.split('\0')
215 copymap[f] = c
217 copymap[f] = c
216 map[f] = e[:4]
218 map[f] = e[:4]
217 pos = newpos
219 pos = newpos
218
220
219 def read(self):
221 def read(self):
220 self.map = {}
222 self.map = {}
221 self.pl = [nullid, nullid]
223 self.pl = [nullid, nullid]
222 try:
224 try:
223 if self.fp:
225 if self.fp:
224 self.fp.seek(0)
226 self.fp.seek(0)
225 st = self.fp.read()
227 st = self.fp.read()
226 self.fp = None
228 self.fp = None
227 else:
229 else:
228 st = self.opener("dirstate").read()
230 st = self.opener("dirstate").read()
229 if st:
231 if st:
230 self.parse(st)
232 self.parse(st)
231 except IOError, err:
233 except IOError, err:
232 if err.errno != errno.ENOENT: raise
234 if err.errno != errno.ENOENT: raise
233
235
234 def reload(self):
236 def reload(self):
235 def mtime():
237 def mtime():
236 m = self.map and self.map.get('.hgignore')
238 m = self.map and self.map.get('.hgignore')
237 return m and m[-1]
239 return m and m[-1]
238
240
239 old_mtime = self.ignorefunc and mtime()
241 old_mtime = self.ignorefunc and mtime()
240 self.read()
242 self.read()
241 if old_mtime != mtime():
243 if old_mtime != mtime():
242 self.ignorefunc = None
244 self.ignorefunc = None
243
245
244 def copy(self, source, dest):
246 def copy(self, source, dest):
245 self.lazyread()
247 self.lazyread()
246 self.markdirty()
248 self.markdirty()
247 self.copymap[dest] = source
249 self.copymap[dest] = source
248
250
249 def copied(self, file):
251 def copied(self, file):
250 return self.copymap.get(file, None)
252 return self.copymap.get(file, None)
251
253
252 def copies(self):
254 def copies(self):
253 return self.copymap
255 return self.copymap
254
256
255 def initdirs(self):
257 def initdirs(self):
256 if self.dirs is None:
258 if self.dirs is None:
257 self.dirs = {}
259 self.dirs = {}
258 for f in self.map:
260 for f in self.map:
259 self.updatedirs(f, 1)
261 self.updatedirs(f, 1)
260
262
261 def updatedirs(self, path, delta):
263 def updatedirs(self, path, delta):
262 if self.dirs is not None:
264 if self.dirs is not None:
263 for c in strutil.findall(path, '/'):
265 for c in strutil.findall(path, '/'):
264 pc = path[:c]
266 pc = path[:c]
265 self.dirs.setdefault(pc, 0)
267 self.dirs.setdefault(pc, 0)
266 self.dirs[pc] += delta
268 self.dirs[pc] += delta
267
269
268 def checkinterfering(self, files):
270 def checkinterfering(self, files):
269 def prefixes(f):
271 def prefixes(f):
270 for c in strutil.rfindall(f, '/'):
272 for c in strutil.rfindall(f, '/'):
271 yield f[:c]
273 yield f[:c]
272 self.lazyread()
274 self.lazyread()
273 self.initdirs()
275 self.initdirs()
274 seendirs = {}
276 seendirs = {}
275 for f in files:
277 for f in files:
276 # shadows
278 # shadows
277 if self.dirs.get(f):
279 if self.dirs.get(f):
278 raise util.Abort(_('directory named %r already in dirstate') %
280 raise util.Abort(_('directory named %r already in dirstate') %
279 f)
281 f)
280 for d in prefixes(f):
282 for d in prefixes(f):
281 if d in seendirs:
283 if d in seendirs:
282 break
284 break
283 if d in self.map:
285 if d in self.map:
284 raise util.Abort(_('file named %r already in dirstate') %
286 raise util.Abort(_('file named %r already in dirstate') %
285 d)
287 d)
286 seendirs[d] = True
288 seendirs[d] = True
287 # disallowed
289 # disallowed
288 if '\r' in f or '\n' in f:
290 if '\r' in f or '\n' in f:
289 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
291 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
290
292
291 def update(self, files, state, **kw):
293 def update(self, files, state, **kw):
292 ''' current states:
294 ''' current states:
293 n normal
295 n normal
294 m needs merging
296 m needs merging
295 r marked for removal
297 r marked for removal
296 a marked for addition'''
298 a marked for addition'''
297
299
298 if not files: return
300 if not files: return
299 self.lazyread()
301 self.lazyread()
300 self.markdirty()
302 self.markdirty()
301 if state == "a":
303 if state == "a":
302 self.initdirs()
304 self.initdirs()
303 self.checkinterfering(files)
305 self.checkinterfering(files)
304 for f in files:
306 for f in files:
305 if state == "r":
307 if state == "r":
306 self.map[f] = ('r', 0, 0, 0)
308 self.map[f] = ('r', 0, 0, 0)
307 self.updatedirs(f, -1)
309 self.updatedirs(f, -1)
308 else:
310 else:
309 if state == "a":
311 if state == "a":
310 self.updatedirs(f, 1)
312 self.updatedirs(f, 1)
311 s = os.lstat(self.wjoin(f))
313 s = os.lstat(self.wjoin(f))
312 st_size = kw.get('st_size', s.st_size)
314 st_size = kw.get('st_size', s.st_size)
313 st_mtime = kw.get('st_mtime', s.st_mtime)
315 st_mtime = kw.get('st_mtime', s.st_mtime)
314 self.map[f] = (state, s.st_mode, st_size, st_mtime)
316 self.map[f] = (state, s.st_mode, st_size, st_mtime)
315 if self.copymap.has_key(f):
317 if self.copymap.has_key(f):
316 del self.copymap[f]
318 del self.copymap[f]
317
319
318 def forget(self, files):
320 def forget(self, files):
319 if not files: return
321 if not files: return
320 self.lazyread()
322 self.lazyread()
321 self.markdirty()
323 self.markdirty()
322 self.initdirs()
324 self.initdirs()
323 for f in files:
325 for f in files:
324 try:
326 try:
325 del self.map[f]
327 del self.map[f]
326 self.updatedirs(f, -1)
328 self.updatedirs(f, -1)
327 except KeyError:
329 except KeyError:
328 self.ui.warn(_("not in dirstate: %s!\n") % f)
330 self.ui.warn(_("not in dirstate: %s!\n") % f)
329 pass
331 pass
330
332
331 def clear(self):
333 def clear(self):
332 self.map = {}
334 self.map = {}
333 self.copymap = {}
335 self.copymap = {}
334 self.dirs = None
336 self.dirs = None
335 self.markdirty()
337 self.markdirty()
336
338
337 def rebuild(self, parent, files):
339 def rebuild(self, parent, files):
338 self.clear()
340 self.clear()
339 for f in files:
341 for f in files:
340 if files.execf(f):
342 if files.execf(f):
341 self.map[f] = ('n', 0777, -1, 0)
343 self.map[f] = ('n', 0777, -1, 0)
342 else:
344 else:
343 self.map[f] = ('n', 0666, -1, 0)
345 self.map[f] = ('n', 0666, -1, 0)
344 self.pl = (parent, nullid)
346 self.pl = (parent, nullid)
345 self.markdirty()
347 self.markdirty()
346
348
347 def write(self):
349 def write(self):
348 if not self.dirty:
350 if not self.dirty:
349 return
351 return
350 cs = cStringIO.StringIO()
352 cs = cStringIO.StringIO()
351 cs.write("".join(self.pl))
353 cs.write("".join(self.pl))
352 for f, e in self.map.iteritems():
354 for f, e in self.map.iteritems():
353 c = self.copied(f)
355 c = self.copied(f)
354 if c:
356 if c:
355 f = f + "\0" + c
357 f = f + "\0" + c
356 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
358 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
357 cs.write(e)
359 cs.write(e)
358 cs.write(f)
360 cs.write(f)
359 st = self.opener("dirstate", "w", atomic=True)
361 st = self.opener("dirstate", "w", atomic=True)
360 st.write(cs.getvalue())
362 st.write(cs.getvalue())
361 self.dirty = 0
363 self.dirty = 0
362
364
363 def filterfiles(self, files):
365 def filterfiles(self, files):
364 ret = {}
366 ret = {}
365 unknown = []
367 unknown = []
366
368
367 for x in files:
369 for x in files:
368 if x == '.':
370 if x == '.':
369 return self.map.copy()
371 return self.map.copy()
370 if x not in self.map:
372 if x not in self.map:
371 unknown.append(x)
373 unknown.append(x)
372 else:
374 else:
373 ret[x] = self.map[x]
375 ret[x] = self.map[x]
374
376
375 if not unknown:
377 if not unknown:
376 return ret
378 return ret
377
379
378 b = self.map.keys()
380 b = self.map.keys()
379 b.sort()
381 b.sort()
380 blen = len(b)
382 blen = len(b)
381
383
382 for x in unknown:
384 for x in unknown:
383 bs = bisect.bisect(b, "%s%s" % (x, '/'))
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
384 while bs < blen:
386 while bs < blen:
385 s = b[bs]
387 s = b[bs]
386 if len(s) > len(x) and s.startswith(x):
388 if len(s) > len(x) and s.startswith(x):
387 ret[s] = self.map[s]
389 ret[s] = self.map[s]
388 else:
390 else:
389 break
391 break
390 bs += 1
392 bs += 1
391 return ret
393 return ret
392
394
393 def supported_type(self, f, st, verbose=False):
395 def supported_type(self, f, st, verbose=False):
394 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
396 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
395 return True
397 return True
396 if verbose:
398 if verbose:
397 kind = 'unknown'
399 kind = 'unknown'
398 if stat.S_ISCHR(st.st_mode): kind = _('character device')
400 if stat.S_ISCHR(st.st_mode): kind = _('character device')
399 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
401 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
400 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
402 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
401 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
403 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
402 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
404 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
403 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
405 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
404 util.pathto(self.root, self.getcwd(), f),
406 util.pathto(self.root, self.getcwd(), f),
405 kind))
407 kind))
406 return False
408 return False
407
409
408 def walk(self, files=None, match=util.always, badmatch=None):
410 def walk(self, files=None, match=util.always, badmatch=None):
409 # filter out the stat
411 # filter out the stat
410 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
412 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
411 yield src, f
413 yield src, f
412
414
413 def statwalk(self, files=None, match=util.always, ignored=False,
415 def statwalk(self, files=None, match=util.always, ignored=False,
414 badmatch=None, directories=False):
416 badmatch=None, directories=False):
415 '''
417 '''
416 walk recursively through the directory tree, finding all files
418 walk recursively through the directory tree, finding all files
417 matched by the match function
419 matched by the match function
418
420
419 results are yielded in a tuple (src, filename, st), where src
421 results are yielded in a tuple (src, filename, st), where src
420 is one of:
422 is one of:
421 'f' the file was found in the directory tree
423 'f' the file was found in the directory tree
422 'd' the file is a directory of the tree
424 'd' the file is a directory of the tree
423 'm' the file was only in the dirstate and not in the tree
425 'm' the file was only in the dirstate and not in the tree
424 'b' file was not found and matched badmatch
426 'b' file was not found and matched badmatch
425
427
426 and st is the stat result if the file was found in the directory.
428 and st is the stat result if the file was found in the directory.
427 '''
429 '''
428 self.lazyread()
430 self.lazyread()
429
431
430 # walk all files by default
432 # walk all files by default
431 if not files:
433 if not files:
432 files = ['.']
434 files = ['.']
433 dc = self.map.copy()
435 dc = self.map.copy()
434 else:
436 else:
435 files = util.unique(files)
437 files = util.unique(files)
436 dc = self.filterfiles(files)
438 dc = self.filterfiles(files)
437
439
438 def imatch(file_):
440 def imatch(file_):
439 if file_ not in dc and self.ignore(file_):
441 if file_ not in dc and self.ignore(file_):
440 return False
442 return False
441 return match(file_)
443 return match(file_)
442
444
443 ignore = self.ignore
445 ignore = self.ignore
444 if ignored:
446 if ignored:
445 imatch = match
447 imatch = match
446 ignore = util.never
448 ignore = util.never
447
449
448 # self.root may end with a path separator when self.root == '/'
450 # self.root may end with a path separator when self.root == '/'
449 common_prefix_len = len(self.root)
451 common_prefix_len = len(self.root)
450 if not self.root.endswith(os.sep):
452 if not self.root.endswith(os.sep):
451 common_prefix_len += 1
453 common_prefix_len += 1
452 # recursion free walker, faster than os.walk.
454 # recursion free walker, faster than os.walk.
453 def findfiles(s):
455 def findfiles(s):
454 work = [s]
456 work = [s]
455 if directories:
457 if directories:
456 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
458 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
457 while work:
459 while work:
458 top = work.pop()
460 top = work.pop()
459 names = os.listdir(top)
461 names = os.listdir(top)
460 names.sort()
462 names.sort()
461 # nd is the top of the repository dir tree
463 # nd is the top of the repository dir tree
462 nd = util.normpath(top[common_prefix_len:])
464 nd = util.normpath(top[common_prefix_len:])
463 if nd == '.':
465 if nd == '.':
464 nd = ''
466 nd = ''
465 else:
467 else:
466 # do not recurse into a repo contained in this
468 # do not recurse into a repo contained in this
467 # one. use bisect to find .hg directory so speed
469 # one. use bisect to find .hg directory so speed
468 # is good on big directory.
470 # is good on big directory.
469 hg = bisect.bisect_left(names, '.hg')
471 hg = bisect.bisect_left(names, '.hg')
470 if hg < len(names) and names[hg] == '.hg':
472 if hg < len(names) and names[hg] == '.hg':
471 if os.path.isdir(os.path.join(top, '.hg')):
473 if os.path.isdir(os.path.join(top, '.hg')):
472 continue
474 continue
473 for f in names:
475 for f in names:
474 np = util.pconvert(os.path.join(nd, f))
476 np = util.pconvert(os.path.join(nd, f))
475 if seen(np):
477 if seen(np):
476 continue
478 continue
477 p = os.path.join(top, f)
479 p = os.path.join(top, f)
478 # don't trip over symlinks
480 # don't trip over symlinks
479 st = os.lstat(p)
481 st = os.lstat(p)
480 if stat.S_ISDIR(st.st_mode):
482 if stat.S_ISDIR(st.st_mode):
481 if not ignore(np):
483 if not ignore(np):
482 work.append(p)
484 work.append(p)
483 if directories:
485 if directories:
484 yield 'd', np, st
486 yield 'd', np, st
485 if imatch(np) and np in dc:
487 if imatch(np) and np in dc:
486 yield 'm', np, st
488 yield 'm', np, st
487 elif imatch(np):
489 elif imatch(np):
488 if self.supported_type(np, st):
490 if self.supported_type(np, st):
489 yield 'f', np, st
491 yield 'f', np, st
490 elif np in dc:
492 elif np in dc:
491 yield 'm', np, st
493 yield 'm', np, st
492
494
493 known = {'.hg': 1}
495 known = {'.hg': 1}
494 def seen(fn):
496 def seen(fn):
495 if fn in known: return True
497 if fn in known: return True
496 known[fn] = 1
498 known[fn] = 1
497
499
498 # step one, find all files that match our criteria
500 # step one, find all files that match our criteria
499 files.sort()
501 files.sort()
500 for ff in files:
502 for ff in files:
501 nf = util.normpath(ff)
503 nf = util.normpath(ff)
502 f = self.wjoin(ff)
504 f = self.wjoin(ff)
503 try:
505 try:
504 st = os.lstat(f)
506 st = os.lstat(f)
505 except OSError, inst:
507 except OSError, inst:
506 found = False
508 found = False
507 for fn in dc:
509 for fn in dc:
508 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
510 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
509 found = True
511 found = True
510 break
512 break
511 if not found:
513 if not found:
512 if inst.errno != errno.ENOENT or not badmatch:
514 if inst.errno != errno.ENOENT or not badmatch:
513 self.ui.warn('%s: %s\n' % (
515 self.ui.warn('%s: %s\n' % (
514 util.pathto(self.root, self.getcwd(), ff),
516 util.pathto(self.root, self.getcwd(), ff),
515 inst.strerror))
517 inst.strerror))
516 elif badmatch and badmatch(ff) and imatch(nf):
518 elif badmatch and badmatch(ff) and imatch(nf):
517 yield 'b', ff, None
519 yield 'b', ff, None
518 continue
520 continue
519 if stat.S_ISDIR(st.st_mode):
521 if stat.S_ISDIR(st.st_mode):
520 cmp1 = (lambda x, y: cmp(x[1], y[1]))
522 cmp1 = (lambda x, y: cmp(x[1], y[1]))
521 sorted_ = [ x for x in findfiles(f) ]
523 sorted_ = [ x for x in findfiles(f) ]
522 sorted_.sort(cmp1)
524 sorted_.sort(cmp1)
523 for e in sorted_:
525 for e in sorted_:
524 yield e
526 yield e
525 else:
527 else:
526 if not seen(nf) and match(nf):
528 if not seen(nf) and match(nf):
527 if self.supported_type(ff, st, verbose=True):
529 if self.supported_type(ff, st, verbose=True):
528 yield 'f', nf, st
530 yield 'f', nf, st
529 elif ff in dc:
531 elif ff in dc:
530 yield 'm', nf, st
532 yield 'm', nf, st
531
533
532 # step two run through anything left in the dc hash and yield
534 # step two run through anything left in the dc hash and yield
533 # if we haven't already seen it
535 # if we haven't already seen it
534 ks = dc.keys()
536 ks = dc.keys()
535 ks.sort()
537 ks.sort()
536 for k in ks:
538 for k in ks:
537 if not seen(k) and imatch(k):
539 if not seen(k) and imatch(k):
538 yield 'm', k, None
540 yield 'm', k, None
539
541
540 def status(self, files=None, match=util.always, list_ignored=False,
542 def status(self, files=None, match=util.always, list_ignored=False,
541 list_clean=False):
543 list_clean=False):
542 lookup, modified, added, unknown, ignored = [], [], [], [], []
544 lookup, modified, added, unknown, ignored = [], [], [], [], []
543 removed, deleted, clean = [], [], []
545 removed, deleted, clean = [], [], []
544
546
545 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
547 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
546 try:
548 try:
547 type_, mode, size, time = self[fn]
549 type_, mode, size, time = self[fn]
548 except KeyError:
550 except KeyError:
549 if list_ignored and self.ignore(fn):
551 if list_ignored and self.ignore(fn):
550 ignored.append(fn)
552 ignored.append(fn)
551 else:
553 else:
552 unknown.append(fn)
554 unknown.append(fn)
553 continue
555 continue
554 if src == 'm':
556 if src == 'm':
555 nonexistent = True
557 nonexistent = True
556 if not st:
558 if not st:
557 try:
559 try:
558 st = os.lstat(self.wjoin(fn))
560 st = os.lstat(self.wjoin(fn))
559 except OSError, inst:
561 except OSError, inst:
560 if inst.errno != errno.ENOENT:
562 if inst.errno != errno.ENOENT:
561 raise
563 raise
562 st = None
564 st = None
563 # We need to re-check that it is a valid file
565 # We need to re-check that it is a valid file
564 if st and self.supported_type(fn, st):
566 if st and self.supported_type(fn, st):
565 nonexistent = False
567 nonexistent = False
566 # XXX: what to do with file no longer present in the fs
568 # XXX: what to do with file no longer present in the fs
567 # who are not removed in the dirstate ?
569 # who are not removed in the dirstate ?
568 if nonexistent and type_ in "nm":
570 if nonexistent and type_ in "nm":
569 deleted.append(fn)
571 deleted.append(fn)
570 continue
572 continue
571 # check the common case first
573 # check the common case first
572 if type_ == 'n':
574 if type_ == 'n':
573 if not st:
575 if not st:
574 st = os.lstat(self.wjoin(fn))
576 st = os.lstat(self.wjoin(fn))
575 if size >= 0 and (size != st.st_size
577 if size >= 0 and (size != st.st_size
576 or (mode ^ st.st_mode) & 0100):
578 or (mode ^ st.st_mode) & 0100):
577 modified.append(fn)
579 modified.append(fn)
578 elif time != int(st.st_mtime):
580 elif time != int(st.st_mtime):
579 lookup.append(fn)
581 lookup.append(fn)
580 elif list_clean:
582 elif list_clean:
581 clean.append(fn)
583 clean.append(fn)
582 elif type_ == 'm':
584 elif type_ == 'm':
583 modified.append(fn)
585 modified.append(fn)
584 elif type_ == 'a':
586 elif type_ == 'a':
585 added.append(fn)
587 added.append(fn)
586 elif type_ == 'r':
588 elif type_ == 'r':
587 removed.append(fn)
589 removed.append(fn)
588
590
589 return (lookup, modified, added, removed, deleted, unknown, ignored,
591 return (lookup, modified, added, removed, deleted, unknown, ignored,
590 clean)
592 clean)
@@ -1,45 +1,64 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init
3 hg init
4
5 # Test issue 562: .hgignore requires newline at end
6 touch foo
7 touch bar
8 touch baz
9 cat > makeignore.py <<EOF
10 f = open(".hgignore", "w")
11 f.write("ignore\n")
12 f.write("foo\n")
13 # No EOL here
14 f.write("bar")
15 f.close()
16 EOF
17
18 python makeignore.py
19 echo % should display baz only
20 hg status
21 rm foo bar baz .hgignore makeignore.py
22
4 touch a.o
23 touch a.o
5 touch a.c
24 touch a.c
6 touch syntax
25 touch syntax
7 mkdir dir
26 mkdir dir
8 touch dir/a.o
27 touch dir/a.o
9 touch dir/b.o
28 touch dir/b.o
10 touch dir/c.o
29 touch dir/c.o
11
30
12 hg add dir/a.o
31 hg add dir/a.o
13 hg commit -m 0
32 hg commit -m 0
14 hg add dir/b.o
33 hg add dir/b.o
15
34
16 echo "--" ; hg status
35 echo "--" ; hg status
17
36
18 echo "*.o" > .hgignore
37 echo "*.o" > .hgignore
19 echo "--" ; hg status 2>&1 | sed -e 's/abort: .*\.hgignore:/abort: .hgignore:/'
38 echo "--" ; hg status 2>&1 | sed -e 's/abort: .*\.hgignore:/abort: .hgignore:/'
20
39
21 echo ".*\.o" > .hgignore
40 echo ".*\.o" > .hgignore
22 echo "--" ; hg status
41 echo "--" ; hg status
23
42
24 # XXX: broken
43 # XXX: broken
25 #echo "glob:**.o" > .hgignore
44 #echo "glob:**.o" > .hgignore
26 #echo "--" ; hg status
45 #echo "--" ; hg status
27 #
46 #
28 #echo "glob:*.o" > .hgignore
47 #echo "glob:*.o" > .hgignore
29 #echo "--" ; hg status
48 #echo "--" ; hg status
30
49
31 echo "syntax: invalid" > .hgignore
50 echo "syntax: invalid" > .hgignore
32 echo "--" ; hg status 2>&1 | sed -e 's/.*\.hgignore:/.hgignore:/'
51 echo "--" ; hg status 2>&1 | sed -e 's/.*\.hgignore:/.hgignore:/'
33
52
34 echo "syntax: glob" > .hgignore
53 echo "syntax: glob" > .hgignore
35 echo "*.o" >> .hgignore
54 echo "*.o" >> .hgignore
36 echo "--" ; hg status
55 echo "--" ; hg status
37
56
38 echo "relglob:syntax*" > .hgignore
57 echo "relglob:syntax*" > .hgignore
39 echo "--" ; hg status
58 echo "--" ; hg status
40
59
41 echo "relglob:*" > .hgignore
60 echo "relglob:*" > .hgignore
42 echo "--" ; hg status
61 echo "--" ; hg status
43
62
44 cd dir
63 cd dir
45 echo "--" ; hg status .
64 echo "--" ; hg status .
@@ -1,36 +1,38 b''
1 % should display baz only
2 ? baz
1 --
3 --
2 A dir/b.o
4 A dir/b.o
3 ? a.c
5 ? a.c
4 ? a.o
6 ? a.o
5 ? dir/c.o
7 ? dir/c.o
6 ? syntax
8 ? syntax
7 --
9 --
8 abort: .hgignore: invalid pattern (relre): *.o
10 abort: .hgignore: invalid pattern (relre): *.o
9 --
11 --
10 A dir/b.o
12 A dir/b.o
11 ? .hgignore
13 ? .hgignore
12 ? a.c
14 ? a.c
13 ? syntax
15 ? syntax
14 --
16 --
15 .hgignore: ignoring invalid syntax 'invalid'
17 .hgignore: ignoring invalid syntax 'invalid'
16 A dir/b.o
18 A dir/b.o
17 ? .hgignore
19 ? .hgignore
18 ? a.c
20 ? a.c
19 ? a.o
21 ? a.o
20 ? dir/c.o
22 ? dir/c.o
21 ? syntax
23 ? syntax
22 --
24 --
23 A dir/b.o
25 A dir/b.o
24 ? .hgignore
26 ? .hgignore
25 ? a.c
27 ? a.c
26 ? syntax
28 ? syntax
27 --
29 --
28 A dir/b.o
30 A dir/b.o
29 ? .hgignore
31 ? .hgignore
30 ? a.c
32 ? a.c
31 ? a.o
33 ? a.o
32 ? dir/c.o
34 ? dir/c.o
33 --
35 --
34 A dir/b.o
36 A dir/b.o
35 --
37 --
36 A b.o
38 A b.o
General Comments 0
You need to be logged in to leave comments. Login now