##// END OF EJS Templates
dirstate: do not ignore current directory '.' (issue 1078)
Patrick Mezard -
r6479:31abcae3 default
parent child Browse files
Show More
@@ -1,635 +1,637 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 Copyright 2005-2007 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 nullid
10 from node import nullid
11 from i18n import _
11 from i18n import _
12 import struct, os, bisect, stat, strutil, util, errno, ignore
12 import struct, os, bisect, stat, strutil, util, errno, ignore
13 import cStringIO, osutil, sys
13 import cStringIO, osutil, sys
14
14
15 _unknown = ('?', 0, 0, 0)
15 _unknown = ('?', 0, 0, 0)
16 _format = ">cllll"
16 _format = ">cllll"
17
17
18 class dirstate(object):
18 class dirstate(object):
19
19
20 def __init__(self, opener, ui, root):
20 def __init__(self, opener, ui, root):
21 self._opener = opener
21 self._opener = opener
22 self._root = root
22 self._root = root
23 self._dirty = False
23 self._dirty = False
24 self._dirtypl = False
24 self._dirtypl = False
25 self._ui = ui
25 self._ui = ui
26
26
27 def __getattr__(self, name):
27 def __getattr__(self, name):
28 if name == '_map':
28 if name == '_map':
29 self._read()
29 self._read()
30 return self._map
30 return self._map
31 elif name == '_copymap':
31 elif name == '_copymap':
32 self._read()
32 self._read()
33 return self._copymap
33 return self._copymap
34 elif name == '_branch':
34 elif name == '_branch':
35 try:
35 try:
36 self._branch = (self._opener("branch").read().strip()
36 self._branch = (self._opener("branch").read().strip()
37 or "default")
37 or "default")
38 except IOError:
38 except IOError:
39 self._branch = "default"
39 self._branch = "default"
40 return self._branch
40 return self._branch
41 elif name == '_pl':
41 elif name == '_pl':
42 self._pl = [nullid, nullid]
42 self._pl = [nullid, nullid]
43 try:
43 try:
44 st = self._opener("dirstate").read(40)
44 st = self._opener("dirstate").read(40)
45 if len(st) == 40:
45 if len(st) == 40:
46 self._pl = st[:20], st[20:40]
46 self._pl = st[:20], st[20:40]
47 except IOError, err:
47 except IOError, err:
48 if err.errno != errno.ENOENT: raise
48 if err.errno != errno.ENOENT: raise
49 return self._pl
49 return self._pl
50 elif name == '_dirs':
50 elif name == '_dirs':
51 self._dirs = {}
51 self._dirs = {}
52 for f in self._map:
52 for f in self._map:
53 if self[f] != 'r':
53 if self[f] != 'r':
54 self._incpath(f)
54 self._incpath(f)
55 return self._dirs
55 return self._dirs
56 elif name == '_ignore':
56 elif name == '_ignore':
57 files = [self._join('.hgignore')]
57 files = [self._join('.hgignore')]
58 for name, path in self._ui.configitems("ui"):
58 for name, path in self._ui.configitems("ui"):
59 if name == 'ignore' or name.startswith('ignore.'):
59 if name == 'ignore' or name.startswith('ignore.'):
60 files.append(os.path.expanduser(path))
60 files.append(os.path.expanduser(path))
61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
62 return self._ignore
62 return self._ignore
63 elif name == '_slash':
63 elif name == '_slash':
64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
65 return self._slash
65 return self._slash
66 elif name == '_checkexec':
66 elif name == '_checkexec':
67 self._checkexec = util.checkexec(self._root)
67 self._checkexec = util.checkexec(self._root)
68 return self._checkexec
68 return self._checkexec
69 else:
69 else:
70 raise AttributeError, name
70 raise AttributeError, name
71
71
72 def _join(self, f):
72 def _join(self, f):
73 return os.path.join(self._root, f)
73 return os.path.join(self._root, f)
74
74
75 def getcwd(self):
75 def getcwd(self):
76 cwd = os.getcwd()
76 cwd = os.getcwd()
77 if cwd == self._root: return ''
77 if cwd == self._root: return ''
78 # self._root ends with a path separator if self._root is '/' or 'C:\'
78 # self._root ends with a path separator if self._root is '/' or 'C:\'
79 rootsep = self._root
79 rootsep = self._root
80 if not util.endswithsep(rootsep):
80 if not util.endswithsep(rootsep):
81 rootsep += os.sep
81 rootsep += os.sep
82 if cwd.startswith(rootsep):
82 if cwd.startswith(rootsep):
83 return cwd[len(rootsep):]
83 return cwd[len(rootsep):]
84 else:
84 else:
85 # we're outside the repo. return an absolute path.
85 # we're outside the repo. return an absolute path.
86 return cwd
86 return cwd
87
87
88 def pathto(self, f, cwd=None):
88 def pathto(self, f, cwd=None):
89 if cwd is None:
89 if cwd is None:
90 cwd = self.getcwd()
90 cwd = self.getcwd()
91 path = util.pathto(self._root, cwd, f)
91 path = util.pathto(self._root, cwd, f)
92 if self._slash:
92 if self._slash:
93 return util.normpath(path)
93 return util.normpath(path)
94 return path
94 return path
95
95
96 def __getitem__(self, key):
96 def __getitem__(self, key):
97 ''' current states:
97 ''' current states:
98 n normal
98 n normal
99 m needs merging
99 m needs merging
100 r marked for removal
100 r marked for removal
101 a marked for addition
101 a marked for addition
102 ? not tracked'''
102 ? not tracked'''
103 return self._map.get(key, ("?",))[0]
103 return self._map.get(key, ("?",))[0]
104
104
105 def __contains__(self, key):
105 def __contains__(self, key):
106 return key in self._map
106 return key in self._map
107
107
108 def __iter__(self):
108 def __iter__(self):
109 a = self._map.keys()
109 a = self._map.keys()
110 a.sort()
110 a.sort()
111 for x in a:
111 for x in a:
112 yield x
112 yield x
113
113
114 def parents(self):
114 def parents(self):
115 return self._pl
115 return self._pl
116
116
117 def branch(self):
117 def branch(self):
118 return self._branch
118 return self._branch
119
119
120 def setparents(self, p1, p2=nullid):
120 def setparents(self, p1, p2=nullid):
121 self._dirty = self._dirtypl = True
121 self._dirty = self._dirtypl = True
122 self._pl = p1, p2
122 self._pl = p1, p2
123
123
124 def setbranch(self, branch):
124 def setbranch(self, branch):
125 self._branch = branch
125 self._branch = branch
126 self._opener("branch", "w").write(branch + '\n')
126 self._opener("branch", "w").write(branch + '\n')
127
127
128 def _read(self):
128 def _read(self):
129 self._map = {}
129 self._map = {}
130 self._copymap = {}
130 self._copymap = {}
131 if not self._dirtypl:
131 if not self._dirtypl:
132 self._pl = [nullid, nullid]
132 self._pl = [nullid, nullid]
133 try:
133 try:
134 st = self._opener("dirstate").read()
134 st = self._opener("dirstate").read()
135 except IOError, err:
135 except IOError, err:
136 if err.errno != errno.ENOENT: raise
136 if err.errno != errno.ENOENT: raise
137 return
137 return
138 if not st:
138 if not st:
139 return
139 return
140
140
141 if not self._dirtypl:
141 if not self._dirtypl:
142 self._pl = [st[:20], st[20: 40]]
142 self._pl = [st[:20], st[20: 40]]
143
143
144 # deref fields so they will be local in loop
144 # deref fields so they will be local in loop
145 dmap = self._map
145 dmap = self._map
146 copymap = self._copymap
146 copymap = self._copymap
147 unpack = struct.unpack
147 unpack = struct.unpack
148 e_size = struct.calcsize(_format)
148 e_size = struct.calcsize(_format)
149 pos1 = 40
149 pos1 = 40
150 l = len(st)
150 l = len(st)
151
151
152 # the inner loop
152 # the inner loop
153 while pos1 < l:
153 while pos1 < l:
154 pos2 = pos1 + e_size
154 pos2 = pos1 + e_size
155 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
155 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
156 pos1 = pos2 + e[4]
156 pos1 = pos2 + e[4]
157 f = st[pos2:pos1]
157 f = st[pos2:pos1]
158 if '\0' in f:
158 if '\0' in f:
159 f, c = f.split('\0')
159 f, c = f.split('\0')
160 copymap[f] = c
160 copymap[f] = c
161 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
161 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
162
162
163 def invalidate(self):
163 def invalidate(self):
164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
164 for a in "_map _copymap _branch _pl _dirs _ignore".split():
165 if a in self.__dict__:
165 if a in self.__dict__:
166 delattr(self, a)
166 delattr(self, a)
167 self._dirty = False
167 self._dirty = False
168
168
169 def copy(self, source, dest):
169 def copy(self, source, dest):
170 self._dirty = True
170 self._dirty = True
171 self._copymap[dest] = source
171 self._copymap[dest] = source
172
172
173 def copied(self, file):
173 def copied(self, file):
174 return self._copymap.get(file, None)
174 return self._copymap.get(file, None)
175
175
176 def copies(self):
176 def copies(self):
177 return self._copymap
177 return self._copymap
178
178
179 def _incpath(self, path):
179 def _incpath(self, path):
180 c = path.rfind('/')
180 c = path.rfind('/')
181 if c >= 0:
181 if c >= 0:
182 dirs = self._dirs
182 dirs = self._dirs
183 base = path[:c]
183 base = path[:c]
184 if base not in dirs:
184 if base not in dirs:
185 self._incpath(base)
185 self._incpath(base)
186 dirs[base] = 1
186 dirs[base] = 1
187 else:
187 else:
188 dirs[base] += 1
188 dirs[base] += 1
189
189
190 def _decpath(self, path):
190 def _decpath(self, path):
191 c = path.rfind('/')
191 c = path.rfind('/')
192 if c >= 0:
192 if c >= 0:
193 base = path[:c]
193 base = path[:c]
194 dirs = self._dirs
194 dirs = self._dirs
195 if dirs[base] == 1:
195 if dirs[base] == 1:
196 del dirs[base]
196 del dirs[base]
197 self._decpath(base)
197 self._decpath(base)
198 else:
198 else:
199 dirs[base] -= 1
199 dirs[base] -= 1
200
200
201 def _incpathcheck(self, f):
201 def _incpathcheck(self, f):
202 if '\r' in f or '\n' in f:
202 if '\r' in f or '\n' in f:
203 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
203 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
204 % f)
204 % f)
205 # shadows
205 # shadows
206 if f in self._dirs:
206 if f in self._dirs:
207 raise util.Abort(_('directory %r already in dirstate') % f)
207 raise util.Abort(_('directory %r already in dirstate') % f)
208 for c in strutil.rfindall(f, '/'):
208 for c in strutil.rfindall(f, '/'):
209 d = f[:c]
209 d = f[:c]
210 if d in self._dirs:
210 if d in self._dirs:
211 break
211 break
212 if d in self._map and self[d] != 'r':
212 if d in self._map and self[d] != 'r':
213 raise util.Abort(_('file %r in dirstate clashes with %r') %
213 raise util.Abort(_('file %r in dirstate clashes with %r') %
214 (d, f))
214 (d, f))
215 self._incpath(f)
215 self._incpath(f)
216
216
217 def _changepath(self, f, newstate, relaxed=False):
217 def _changepath(self, f, newstate, relaxed=False):
218 # handle upcoming path changes
218 # handle upcoming path changes
219 oldstate = self[f]
219 oldstate = self[f]
220 if oldstate not in "?r" and newstate in "?r":
220 if oldstate not in "?r" and newstate in "?r":
221 if "_dirs" in self.__dict__:
221 if "_dirs" in self.__dict__:
222 self._decpath(f)
222 self._decpath(f)
223 return
223 return
224 if oldstate in "?r" and newstate not in "?r":
224 if oldstate in "?r" and newstate not in "?r":
225 if relaxed and oldstate == '?':
225 if relaxed and oldstate == '?':
226 # XXX
226 # XXX
227 # in relaxed mode we assume the caller knows
227 # in relaxed mode we assume the caller knows
228 # what it is doing, workaround for updating
228 # what it is doing, workaround for updating
229 # dir-to-file revisions
229 # dir-to-file revisions
230 if "_dirs" in self.__dict__:
230 if "_dirs" in self.__dict__:
231 self._incpath(f)
231 self._incpath(f)
232 return
232 return
233 self._incpathcheck(f)
233 self._incpathcheck(f)
234 return
234 return
235
235
236 def normal(self, f):
236 def normal(self, f):
237 'mark a file normal and clean'
237 'mark a file normal and clean'
238 self._dirty = True
238 self._dirty = True
239 self._changepath(f, 'n', True)
239 self._changepath(f, 'n', True)
240 s = os.lstat(self._join(f))
240 s = os.lstat(self._join(f))
241 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
241 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
242 if f in self._copymap:
242 if f in self._copymap:
243 del self._copymap[f]
243 del self._copymap[f]
244
244
245 def normallookup(self, f):
245 def normallookup(self, f):
246 'mark a file normal, but possibly dirty'
246 'mark a file normal, but possibly dirty'
247 if self._pl[1] != nullid and f in self._map:
247 if self._pl[1] != nullid and f in self._map:
248 # if there is a merge going on and the file was either
248 # if there is a merge going on and the file was either
249 # in state 'm' or dirty before being removed, restore that state.
249 # in state 'm' or dirty before being removed, restore that state.
250 entry = self._map[f]
250 entry = self._map[f]
251 if entry[0] == 'r' and entry[2] in (-1, -2):
251 if entry[0] == 'r' and entry[2] in (-1, -2):
252 source = self._copymap.get(f)
252 source = self._copymap.get(f)
253 if entry[2] == -1:
253 if entry[2] == -1:
254 self.merge(f)
254 self.merge(f)
255 elif entry[2] == -2:
255 elif entry[2] == -2:
256 self.normaldirty(f)
256 self.normaldirty(f)
257 if source:
257 if source:
258 self.copy(source, f)
258 self.copy(source, f)
259 return
259 return
260 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
260 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
261 return
261 return
262 self._dirty = True
262 self._dirty = True
263 self._changepath(f, 'n', True)
263 self._changepath(f, 'n', True)
264 self._map[f] = ('n', 0, -1, -1, 0)
264 self._map[f] = ('n', 0, -1, -1, 0)
265 if f in self._copymap:
265 if f in self._copymap:
266 del self._copymap[f]
266 del self._copymap[f]
267
267
268 def normaldirty(self, f):
268 def normaldirty(self, f):
269 'mark a file normal, but dirty'
269 'mark a file normal, but dirty'
270 self._dirty = True
270 self._dirty = True
271 self._changepath(f, 'n', True)
271 self._changepath(f, 'n', True)
272 self._map[f] = ('n', 0, -2, -1, 0)
272 self._map[f] = ('n', 0, -2, -1, 0)
273 if f in self._copymap:
273 if f in self._copymap:
274 del self._copymap[f]
274 del self._copymap[f]
275
275
276 def add(self, f):
276 def add(self, f):
277 'mark a file added'
277 'mark a file added'
278 self._dirty = True
278 self._dirty = True
279 self._changepath(f, 'a')
279 self._changepath(f, 'a')
280 self._map[f] = ('a', 0, -1, -1, 0)
280 self._map[f] = ('a', 0, -1, -1, 0)
281 if f in self._copymap:
281 if f in self._copymap:
282 del self._copymap[f]
282 del self._copymap[f]
283
283
284 def remove(self, f):
284 def remove(self, f):
285 'mark a file removed'
285 'mark a file removed'
286 self._dirty = True
286 self._dirty = True
287 self._changepath(f, 'r')
287 self._changepath(f, 'r')
288 size = 0
288 size = 0
289 if self._pl[1] != nullid and f in self._map:
289 if self._pl[1] != nullid and f in self._map:
290 entry = self._map[f]
290 entry = self._map[f]
291 if entry[0] == 'm':
291 if entry[0] == 'm':
292 size = -1
292 size = -1
293 elif entry[0] == 'n' and entry[2] == -2:
293 elif entry[0] == 'n' and entry[2] == -2:
294 size = -2
294 size = -2
295 self._map[f] = ('r', 0, size, 0, 0)
295 self._map[f] = ('r', 0, size, 0, 0)
296 if size == 0 and f in self._copymap:
296 if size == 0 and f in self._copymap:
297 del self._copymap[f]
297 del self._copymap[f]
298
298
299 def merge(self, f):
299 def merge(self, f):
300 'mark a file merged'
300 'mark a file merged'
301 self._dirty = True
301 self._dirty = True
302 s = os.lstat(self._join(f))
302 s = os.lstat(self._join(f))
303 self._changepath(f, 'm', True)
303 self._changepath(f, 'm', True)
304 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
304 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
305 if f in self._copymap:
305 if f in self._copymap:
306 del self._copymap[f]
306 del self._copymap[f]
307
307
308 def forget(self, f):
308 def forget(self, f):
309 'forget a file'
309 'forget a file'
310 self._dirty = True
310 self._dirty = True
311 try:
311 try:
312 self._changepath(f, '?')
312 self._changepath(f, '?')
313 del self._map[f]
313 del self._map[f]
314 except KeyError:
314 except KeyError:
315 self._ui.warn(_("not in dirstate: %s\n") % f)
315 self._ui.warn(_("not in dirstate: %s\n") % f)
316
316
317 def clear(self):
317 def clear(self):
318 self._map = {}
318 self._map = {}
319 if "_dirs" in self.__dict__:
319 if "_dirs" in self.__dict__:
320 delattr(self, "_dirs");
320 delattr(self, "_dirs");
321 self._copymap = {}
321 self._copymap = {}
322 self._pl = [nullid, nullid]
322 self._pl = [nullid, nullid]
323 self._dirty = True
323 self._dirty = True
324
324
325 def rebuild(self, parent, files):
325 def rebuild(self, parent, files):
326 self.clear()
326 self.clear()
327 for f in files:
327 for f in files:
328 if files.execf(f):
328 if files.execf(f):
329 self._map[f] = ('n', 0777, -1, 0, 0)
329 self._map[f] = ('n', 0777, -1, 0, 0)
330 else:
330 else:
331 self._map[f] = ('n', 0666, -1, 0, 0)
331 self._map[f] = ('n', 0666, -1, 0, 0)
332 self._pl = (parent, nullid)
332 self._pl = (parent, nullid)
333 self._dirty = True
333 self._dirty = True
334
334
335 def write(self):
335 def write(self):
336 if not self._dirty:
336 if not self._dirty:
337 return
337 return
338 st = self._opener("dirstate", "w", atomictemp=True)
338 st = self._opener("dirstate", "w", atomictemp=True)
339
339
340 try:
340 try:
341 gran = int(self._ui.config('dirstate', 'granularity', 1))
341 gran = int(self._ui.config('dirstate', 'granularity', 1))
342 except ValueError:
342 except ValueError:
343 gran = 1
343 gran = 1
344 limit = sys.maxint
344 limit = sys.maxint
345 if gran > 0:
345 if gran > 0:
346 limit = util.fstat(st).st_mtime - gran
346 limit = util.fstat(st).st_mtime - gran
347
347
348 cs = cStringIO.StringIO()
348 cs = cStringIO.StringIO()
349 copymap = self._copymap
349 copymap = self._copymap
350 pack = struct.pack
350 pack = struct.pack
351 write = cs.write
351 write = cs.write
352 write("".join(self._pl))
352 write("".join(self._pl))
353 for f, e in self._map.iteritems():
353 for f, e in self._map.iteritems():
354 if f in copymap:
354 if f in copymap:
355 f = "%s\0%s" % (f, copymap[f])
355 f = "%s\0%s" % (f, copymap[f])
356 if e[3] > limit and e[0] == 'n':
356 if e[3] > limit and e[0] == 'n':
357 e = (e[0], 0, -1, -1, 0)
357 e = (e[0], 0, -1, -1, 0)
358 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
358 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
359 write(e)
359 write(e)
360 write(f)
360 write(f)
361 st.write(cs.getvalue())
361 st.write(cs.getvalue())
362 st.rename()
362 st.rename()
363 self._dirty = self._dirtypl = False
363 self._dirty = self._dirtypl = False
364
364
365 def _filter(self, files):
365 def _filter(self, files):
366 ret = {}
366 ret = {}
367 unknown = []
367 unknown = []
368
368
369 for x in files:
369 for x in files:
370 if x == '.':
370 if x == '.':
371 return self._map.copy()
371 return self._map.copy()
372 if x not in self._map:
372 if x not in self._map:
373 unknown.append(x)
373 unknown.append(x)
374 else:
374 else:
375 ret[x] = self._map[x]
375 ret[x] = self._map[x]
376
376
377 if not unknown:
377 if not unknown:
378 return ret
378 return ret
379
379
380 b = self._map.keys()
380 b = self._map.keys()
381 b.sort()
381 b.sort()
382 blen = len(b)
382 blen = len(b)
383
383
384 for x in unknown:
384 for x in unknown:
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
385 bs = bisect.bisect(b, "%s%s" % (x, '/'))
386 while bs < blen:
386 while bs < blen:
387 s = b[bs]
387 s = b[bs]
388 if len(s) > len(x) and s.startswith(x):
388 if len(s) > len(x) and s.startswith(x):
389 ret[s] = self._map[s]
389 ret[s] = self._map[s]
390 else:
390 else:
391 break
391 break
392 bs += 1
392 bs += 1
393 return ret
393 return ret
394
394
395 def _supported(self, f, mode, verbose=False):
395 def _supported(self, f, mode, verbose=False):
396 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
396 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
397 return True
397 return True
398 if verbose:
398 if verbose:
399 kind = 'unknown'
399 kind = 'unknown'
400 if stat.S_ISCHR(mode): kind = _('character device')
400 if stat.S_ISCHR(mode): kind = _('character device')
401 elif stat.S_ISBLK(mode): kind = _('block device')
401 elif stat.S_ISBLK(mode): kind = _('block device')
402 elif stat.S_ISFIFO(mode): kind = _('fifo')
402 elif stat.S_ISFIFO(mode): kind = _('fifo')
403 elif stat.S_ISSOCK(mode): kind = _('socket')
403 elif stat.S_ISSOCK(mode): kind = _('socket')
404 elif stat.S_ISDIR(mode): kind = _('directory')
404 elif stat.S_ISDIR(mode): kind = _('directory')
405 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
405 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
406 % (self.pathto(f), kind))
406 % (self.pathto(f), kind))
407 return False
407 return False
408
408
409 def _dirignore(self, f):
409 def _dirignore(self, f):
410 if f == '.':
411 return False
410 if self._ignore(f):
412 if self._ignore(f):
411 return True
413 return True
412 for c in strutil.findall(f, '/'):
414 for c in strutil.findall(f, '/'):
413 if self._ignore(f[:c]):
415 if self._ignore(f[:c]):
414 return True
416 return True
415 return False
417 return False
416
418
417 def walk(self, files=None, match=util.always, badmatch=None):
419 def walk(self, files=None, match=util.always, badmatch=None):
418 # filter out the stat
420 # filter out the stat
419 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
421 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
420 yield src, f
422 yield src, f
421
423
422 def statwalk(self, files=None, match=util.always, unknown=True,
424 def statwalk(self, files=None, match=util.always, unknown=True,
423 ignored=False, badmatch=None, directories=False):
425 ignored=False, badmatch=None, directories=False):
424 '''
426 '''
425 walk recursively through the directory tree, finding all files
427 walk recursively through the directory tree, finding all files
426 matched by the match function
428 matched by the match function
427
429
428 results are yielded in a tuple (src, filename, st), where src
430 results are yielded in a tuple (src, filename, st), where src
429 is one of:
431 is one of:
430 'f' the file was found in the directory tree
432 'f' the file was found in the directory tree
431 'd' the file is a directory of the tree
433 'd' the file is a directory of the tree
432 'm' the file was only in the dirstate and not in the tree
434 'm' the file was only in the dirstate and not in the tree
433 'b' file was not found and matched badmatch
435 'b' file was not found and matched badmatch
434
436
435 and st is the stat result if the file was found in the directory.
437 and st is the stat result if the file was found in the directory.
436 '''
438 '''
437
439
438 # walk all files by default
440 # walk all files by default
439 if not files:
441 if not files:
440 files = ['.']
442 files = ['.']
441 dc = self._map.copy()
443 dc = self._map.copy()
442 else:
444 else:
443 files = util.unique(files)
445 files = util.unique(files)
444 dc = self._filter(files)
446 dc = self._filter(files)
445
447
446 def imatch(file_):
448 def imatch(file_):
447 if file_ not in dc and self._ignore(file_):
449 if file_ not in dc and self._ignore(file_):
448 return False
450 return False
449 return match(file_)
451 return match(file_)
450
452
451 # TODO: don't walk unknown directories if unknown and ignored are False
453 # TODO: don't walk unknown directories if unknown and ignored are False
452 ignore = self._ignore
454 ignore = self._ignore
453 dirignore = self._dirignore
455 dirignore = self._dirignore
454 if ignored:
456 if ignored:
455 imatch = match
457 imatch = match
456 ignore = util.never
458 ignore = util.never
457 dirignore = util.never
459 dirignore = util.never
458
460
459 # self._root may end with a path separator when self._root == '/'
461 # self._root may end with a path separator when self._root == '/'
460 common_prefix_len = len(self._root)
462 common_prefix_len = len(self._root)
461 if not util.endswithsep(self._root):
463 if not util.endswithsep(self._root):
462 common_prefix_len += 1
464 common_prefix_len += 1
463
465
464 normpath = util.normpath
466 normpath = util.normpath
465 listdir = osutil.listdir
467 listdir = osutil.listdir
466 lstat = os.lstat
468 lstat = os.lstat
467 bisect_left = bisect.bisect_left
469 bisect_left = bisect.bisect_left
468 isdir = os.path.isdir
470 isdir = os.path.isdir
469 pconvert = util.pconvert
471 pconvert = util.pconvert
470 join = os.path.join
472 join = os.path.join
471 s_isdir = stat.S_ISDIR
473 s_isdir = stat.S_ISDIR
472 supported = self._supported
474 supported = self._supported
473 _join = self._join
475 _join = self._join
474 known = {'.hg': 1}
476 known = {'.hg': 1}
475
477
476 # recursion free walker, faster than os.walk.
478 # recursion free walker, faster than os.walk.
477 def findfiles(s):
479 def findfiles(s):
478 work = [s]
480 work = [s]
479 wadd = work.append
481 wadd = work.append
480 found = []
482 found = []
481 add = found.append
483 add = found.append
482 if directories:
484 if directories:
483 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
485 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
484 while work:
486 while work:
485 top = work.pop()
487 top = work.pop()
486 entries = listdir(top, stat=True)
488 entries = listdir(top, stat=True)
487 # nd is the top of the repository dir tree
489 # nd is the top of the repository dir tree
488 nd = normpath(top[common_prefix_len:])
490 nd = normpath(top[common_prefix_len:])
489 if nd == '.':
491 if nd == '.':
490 nd = ''
492 nd = ''
491 else:
493 else:
492 # do not recurse into a repo contained in this
494 # do not recurse into a repo contained in this
493 # one. use bisect to find .hg directory so speed
495 # one. use bisect to find .hg directory so speed
494 # is good on big directory.
496 # is good on big directory.
495 names = [e[0] for e in entries]
497 names = [e[0] for e in entries]
496 hg = bisect_left(names, '.hg')
498 hg = bisect_left(names, '.hg')
497 if hg < len(names) and names[hg] == '.hg':
499 if hg < len(names) and names[hg] == '.hg':
498 if isdir(join(top, '.hg')):
500 if isdir(join(top, '.hg')):
499 continue
501 continue
500 for f, kind, st in entries:
502 for f, kind, st in entries:
501 np = pconvert(join(nd, f))
503 np = pconvert(join(nd, f))
502 if np in known:
504 if np in known:
503 continue
505 continue
504 known[np] = 1
506 known[np] = 1
505 p = join(top, f)
507 p = join(top, f)
506 # don't trip over symlinks
508 # don't trip over symlinks
507 if kind == stat.S_IFDIR:
509 if kind == stat.S_IFDIR:
508 if not ignore(np):
510 if not ignore(np):
509 wadd(p)
511 wadd(p)
510 if directories:
512 if directories:
511 add((np, 'd', st))
513 add((np, 'd', st))
512 if np in dc and match(np):
514 if np in dc and match(np):
513 add((np, 'm', st))
515 add((np, 'm', st))
514 elif imatch(np):
516 elif imatch(np):
515 if supported(np, st.st_mode):
517 if supported(np, st.st_mode):
516 add((np, 'f', st))
518 add((np, 'f', st))
517 elif np in dc:
519 elif np in dc:
518 add((np, 'm', st))
520 add((np, 'm', st))
519 found.sort()
521 found.sort()
520 return found
522 return found
521
523
522 # step one, find all files that match our criteria
524 # step one, find all files that match our criteria
523 files.sort()
525 files.sort()
524 for ff in files:
526 for ff in files:
525 nf = normpath(ff)
527 nf = normpath(ff)
526 f = _join(ff)
528 f = _join(ff)
527 try:
529 try:
528 st = lstat(f)
530 st = lstat(f)
529 except OSError, inst:
531 except OSError, inst:
530 found = False
532 found = False
531 for fn in dc:
533 for fn in dc:
532 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
534 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
533 found = True
535 found = True
534 break
536 break
535 if not found:
537 if not found:
536 if inst.errno != errno.ENOENT or not badmatch:
538 if inst.errno != errno.ENOENT or not badmatch:
537 self._ui.warn('%s: %s\n' %
539 self._ui.warn('%s: %s\n' %
538 (self.pathto(ff), inst.strerror))
540 (self.pathto(ff), inst.strerror))
539 elif badmatch and badmatch(ff) and imatch(nf):
541 elif badmatch and badmatch(ff) and imatch(nf):
540 yield 'b', ff, None
542 yield 'b', ff, None
541 continue
543 continue
542 if s_isdir(st.st_mode):
544 if s_isdir(st.st_mode):
543 if not dirignore(nf):
545 if not dirignore(nf):
544 for f, src, st in findfiles(f):
546 for f, src, st in findfiles(f):
545 yield src, f, st
547 yield src, f, st
546 else:
548 else:
547 if nf in known:
549 if nf in known:
548 continue
550 continue
549 known[nf] = 1
551 known[nf] = 1
550 if match(nf):
552 if match(nf):
551 if supported(ff, st.st_mode, verbose=True):
553 if supported(ff, st.st_mode, verbose=True):
552 yield 'f', nf, st
554 yield 'f', nf, st
553 elif ff in dc:
555 elif ff in dc:
554 yield 'm', nf, st
556 yield 'm', nf, st
555
557
556 # step two run through anything left in the dc hash and yield
558 # step two run through anything left in the dc hash and yield
557 # if we haven't already seen it
559 # if we haven't already seen it
558 ks = dc.keys()
560 ks = dc.keys()
559 ks.sort()
561 ks.sort()
560 for k in ks:
562 for k in ks:
561 if k in known:
563 if k in known:
562 continue
564 continue
563 known[k] = 1
565 known[k] = 1
564 if imatch(k):
566 if imatch(k):
565 yield 'm', k, None
567 yield 'm', k, None
566
568
567 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
569 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
568 lookup, modified, added, unknown, ignored = [], [], [], [], []
570 lookup, modified, added, unknown, ignored = [], [], [], [], []
569 removed, deleted, clean = [], [], []
571 removed, deleted, clean = [], [], []
570
572
571 files = files or []
573 files = files or []
572 _join = self._join
574 _join = self._join
573 lstat = os.lstat
575 lstat = os.lstat
574 cmap = self._copymap
576 cmap = self._copymap
575 dmap = self._map
577 dmap = self._map
576 ladd = lookup.append
578 ladd = lookup.append
577 madd = modified.append
579 madd = modified.append
578 aadd = added.append
580 aadd = added.append
579 uadd = unknown.append
581 uadd = unknown.append
580 iadd = ignored.append
582 iadd = ignored.append
581 radd = removed.append
583 radd = removed.append
582 dadd = deleted.append
584 dadd = deleted.append
583 cadd = clean.append
585 cadd = clean.append
584
586
585 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
587 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
586 ignored=list_ignored):
588 ignored=list_ignored):
587 if fn in dmap:
589 if fn in dmap:
588 type_, mode, size, time, foo = dmap[fn]
590 type_, mode, size, time, foo = dmap[fn]
589 else:
591 else:
590 if (list_ignored or fn in files) and self._dirignore(fn):
592 if (list_ignored or fn in files) and self._dirignore(fn):
591 if list_ignored:
593 if list_ignored:
592 iadd(fn)
594 iadd(fn)
593 elif list_unknown:
595 elif list_unknown:
594 uadd(fn)
596 uadd(fn)
595 continue
597 continue
596 if src == 'm':
598 if src == 'm':
597 nonexistent = True
599 nonexistent = True
598 if not st:
600 if not st:
599 try:
601 try:
600 st = lstat(_join(fn))
602 st = lstat(_join(fn))
601 except OSError, inst:
603 except OSError, inst:
602 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
604 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
603 raise
605 raise
604 st = None
606 st = None
605 # We need to re-check that it is a valid file
607 # We need to re-check that it is a valid file
606 if st and self._supported(fn, st.st_mode):
608 if st and self._supported(fn, st.st_mode):
607 nonexistent = False
609 nonexistent = False
608 # XXX: what to do with file no longer present in the fs
610 # XXX: what to do with file no longer present in the fs
609 # who are not removed in the dirstate ?
611 # who are not removed in the dirstate ?
610 if nonexistent and type_ in "nma":
612 if nonexistent and type_ in "nma":
611 dadd(fn)
613 dadd(fn)
612 continue
614 continue
613 # check the common case first
615 # check the common case first
614 if type_ == 'n':
616 if type_ == 'n':
615 if not st:
617 if not st:
616 st = lstat(_join(fn))
618 st = lstat(_join(fn))
617 if (size >= 0 and
619 if (size >= 0 and
618 (size != st.st_size
620 (size != st.st_size
619 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
621 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
620 or size == -2
622 or size == -2
621 or fn in self._copymap):
623 or fn in self._copymap):
622 madd(fn)
624 madd(fn)
623 elif time != int(st.st_mtime):
625 elif time != int(st.st_mtime):
624 ladd(fn)
626 ladd(fn)
625 elif list_clean:
627 elif list_clean:
626 cadd(fn)
628 cadd(fn)
627 elif type_ == 'm':
629 elif type_ == 'm':
628 madd(fn)
630 madd(fn)
629 elif type_ == 'a':
631 elif type_ == 'a':
630 aadd(fn)
632 aadd(fn)
631 elif type_ == 'r':
633 elif type_ == 'r':
632 radd(fn)
634 radd(fn)
633
635
634 return (lookup, modified, added, removed, deleted, unknown, ignored,
636 return (lookup, modified, added, removed, deleted, unknown, ignored,
635 clean)
637 clean)
@@ -1,67 +1,71 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init
3 hg init
4
4
5 # Test issue 562: .hgignore requires newline at end
5 # Test issue 562: .hgignore requires newline at end
6 touch foo
6 touch foo
7 touch bar
7 touch bar
8 touch baz
8 touch baz
9 cat > makeignore.py <<EOF
9 cat > makeignore.py <<EOF
10 f = open(".hgignore", "w")
10 f = open(".hgignore", "w")
11 f.write("ignore\n")
11 f.write("ignore\n")
12 f.write("foo\n")
12 f.write("foo\n")
13 # No EOL here
13 # No EOL here
14 f.write("bar")
14 f.write("bar")
15 f.close()
15 f.close()
16 EOF
16 EOF
17
17
18 python makeignore.py
18 python makeignore.py
19 echo % should display baz only
19 echo % should display baz only
20 hg status
20 hg status
21 rm foo bar baz .hgignore makeignore.py
21 rm foo bar baz .hgignore makeignore.py
22
22
23 touch a.o
23 touch a.o
24 touch a.c
24 touch a.c
25 touch syntax
25 touch syntax
26 mkdir dir
26 mkdir dir
27 touch dir/a.o
27 touch dir/a.o
28 touch dir/b.o
28 touch dir/b.o
29 touch dir/c.o
29 touch dir/c.o
30
30
31 hg add dir/a.o
31 hg add dir/a.o
32 hg commit -m 0
32 hg commit -m 0
33 hg add dir/b.o
33 hg add dir/b.o
34
34
35 echo "--" ; hg status
35 echo "--" ; hg status
36
36
37 echo "*.o" > .hgignore
37 echo "*.o" > .hgignore
38 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:/'
39
39
40 echo ".*\.o" > .hgignore
40 echo ".*\.o" > .hgignore
41 echo "--" ; hg status
41 echo "--" ; hg status
42
42
43 # Check it does not ignore the current directory '.'
44 echo "^\." > .hgignore
45 echo "--" ; hg status
46
43 echo "glob:**.o" > .hgignore
47 echo "glob:**.o" > .hgignore
44 echo "--" ; hg status
48 echo "--" ; hg status
45
49
46 echo "glob:*.o" > .hgignore
50 echo "glob:*.o" > .hgignore
47 echo "--" ; hg status
51 echo "--" ; hg status
48
52
49 echo "syntax: glob" > .hgignore
53 echo "syntax: glob" > .hgignore
50 echo "re:.*\.o" >> .hgignore
54 echo "re:.*\.o" >> .hgignore
51 echo "--" ; hg status
55 echo "--" ; hg status
52
56
53 echo "syntax: invalid" > .hgignore
57 echo "syntax: invalid" > .hgignore
54 echo "--" ; hg status 2>&1 | sed -e 's/.*\.hgignore:/.hgignore:/'
58 echo "--" ; hg status 2>&1 | sed -e 's/.*\.hgignore:/.hgignore:/'
55
59
56 echo "syntax: glob" > .hgignore
60 echo "syntax: glob" > .hgignore
57 echo "*.o" >> .hgignore
61 echo "*.o" >> .hgignore
58 echo "--" ; hg status
62 echo "--" ; hg status
59
63
60 echo "relglob:syntax*" > .hgignore
64 echo "relglob:syntax*" > .hgignore
61 echo "--" ; hg status
65 echo "--" ; hg status
62
66
63 echo "relglob:*" > .hgignore
67 echo "relglob:*" > .hgignore
64 echo "--" ; hg status
68 echo "--" ; hg status
65
69
66 cd dir
70 cd dir
67 echo "--" ; hg status .
71 echo "--" ; hg status .
@@ -1,53 +1,59 b''
1 % should display baz only
1 % should display baz only
2 ? baz
2 ? baz
3 --
3 --
4 A dir/b.o
4 A dir/b.o
5 ? a.c
5 ? a.c
6 ? a.o
6 ? a.o
7 ? dir/c.o
7 ? dir/c.o
8 ? syntax
8 ? syntax
9 --
9 --
10 abort: .hgignore: invalid pattern (relre): *.o
10 abort: .hgignore: invalid pattern (relre): *.o
11 --
11 --
12 A dir/b.o
12 A dir/b.o
13 ? .hgignore
13 ? .hgignore
14 ? a.c
14 ? a.c
15 ? syntax
15 ? syntax
16 --
16 --
17 A dir/b.o
17 A dir/b.o
18 ? a.c
19 ? a.o
20 ? dir/c.o
21 ? syntax
22 --
23 A dir/b.o
18 ? .hgignore
24 ? .hgignore
19 ? a.c
25 ? a.c
20 ? syntax
26 ? syntax
21 --
27 --
22 A dir/b.o
28 A dir/b.o
23 ? .hgignore
29 ? .hgignore
24 ? a.c
30 ? a.c
25 ? syntax
31 ? syntax
26 --
32 --
27 A dir/b.o
33 A dir/b.o
28 ? .hgignore
34 ? .hgignore
29 ? a.c
35 ? a.c
30 ? syntax
36 ? syntax
31 --
37 --
32 .hgignore: ignoring invalid syntax 'invalid'
38 .hgignore: ignoring invalid syntax 'invalid'
33 A dir/b.o
39 A dir/b.o
34 ? .hgignore
40 ? .hgignore
35 ? a.c
41 ? a.c
36 ? a.o
42 ? a.o
37 ? dir/c.o
43 ? dir/c.o
38 ? syntax
44 ? syntax
39 --
45 --
40 A dir/b.o
46 A dir/b.o
41 ? .hgignore
47 ? .hgignore
42 ? a.c
48 ? a.c
43 ? syntax
49 ? syntax
44 --
50 --
45 A dir/b.o
51 A dir/b.o
46 ? .hgignore
52 ? .hgignore
47 ? a.c
53 ? a.c
48 ? a.o
54 ? a.o
49 ? dir/c.o
55 ? dir/c.o
50 --
56 --
51 A dir/b.o
57 A dir/b.o
52 --
58 --
53 A b.o
59 A b.o
General Comments 0
You need to be logged in to leave comments. Login now