##// END OF EJS Templates
dirstate.remove: during merges, remember the previous file state...
Alexis S. L. Carvalho -
r6297:fed1a9c2 default
parent child Browse files
Show More
@@ -0,0 +1,29 b''
1 #!/bin/sh
2
3 hg init repo
4 cd repo
5
6 echo foo > foo
7 echo bar > bar
8 hg ci -qAm 'add foo bar'
9
10 echo foo2 >> foo
11 echo bleh > bar
12 hg ci -m 'change foo bar'
13
14 hg up -qC 0
15 hg mv foo foo1
16 echo foo1 > foo1
17 hg cat foo >> foo1
18 hg ci -m 'mv foo foo1'
19
20 hg merge
21 hg debugstate --nodates
22 hg st -q
23
24 echo '% removing foo1 and bar'
25 cp foo1 F
26 cp bar B
27 hg rm -f foo1 bar
28 hg debugstate --nodates
29 hg st -qC
@@ -0,0 +1,15 b''
1 merging foo1 and foo
2 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
3 (branch merge, don't forget to commit)
4 n 0 -2 bar
5 m 644 14 foo1
6 copy: foo -> foo1
7 M bar
8 M foo1
9 % removing foo1 and bar
10 r 0 -2 bar
11 r 0 -1 foo1
12 copy: foo -> foo1
13 R bar
14 R foo1
15 foo
@@ -1,602 +1,609 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
13 import cStringIO, osutil
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 self._dirty = True
247 self._dirty = True
248 self._changepath(f, 'n', True)
248 self._changepath(f, 'n', True)
249 self._map[f] = ('n', 0, -1, -1, 0)
249 self._map[f] = ('n', 0, -1, -1, 0)
250 if f in self._copymap:
250 if f in self._copymap:
251 del self._copymap[f]
251 del self._copymap[f]
252
252
253 def normaldirty(self, f):
253 def normaldirty(self, f):
254 'mark a file normal, but dirty'
254 'mark a file normal, but dirty'
255 self._dirty = True
255 self._dirty = True
256 self._changepath(f, 'n', True)
256 self._changepath(f, 'n', True)
257 self._map[f] = ('n', 0, -2, -1, 0)
257 self._map[f] = ('n', 0, -2, -1, 0)
258 if f in self._copymap:
258 if f in self._copymap:
259 del self._copymap[f]
259 del self._copymap[f]
260
260
261 def add(self, f):
261 def add(self, f):
262 'mark a file added'
262 'mark a file added'
263 self._dirty = True
263 self._dirty = True
264 self._changepath(f, 'a')
264 self._changepath(f, 'a')
265 self._map[f] = ('a', 0, -1, -1, 0)
265 self._map[f] = ('a', 0, -1, -1, 0)
266 if f in self._copymap:
266 if f in self._copymap:
267 del self._copymap[f]
267 del self._copymap[f]
268
268
269 def remove(self, f):
269 def remove(self, f):
270 'mark a file removed'
270 'mark a file removed'
271 self._dirty = True
271 self._dirty = True
272 self._changepath(f, 'r')
272 self._changepath(f, 'r')
273 self._map[f] = ('r', 0, 0, 0, 0)
273 size = 0
274 if f in self._copymap:
274 if self._pl[1] != nullid and f in self._map:
275 entry = self._map[f]
276 if entry[0] == 'm':
277 size = -1
278 elif entry[0] == 'n' and entry[2] == -2:
279 size = -2
280 self._map[f] = ('r', 0, size, 0, 0)
281 if size == 0 and f in self._copymap:
275 del self._copymap[f]
282 del self._copymap[f]
276
283
277 def merge(self, f):
284 def merge(self, f):
278 'mark a file merged'
285 'mark a file merged'
279 self._dirty = True
286 self._dirty = True
280 s = os.lstat(self._join(f))
287 s = os.lstat(self._join(f))
281 self._changepath(f, 'm', True)
288 self._changepath(f, 'm', True)
282 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
289 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
283 if f in self._copymap:
290 if f in self._copymap:
284 del self._copymap[f]
291 del self._copymap[f]
285
292
286 def forget(self, f):
293 def forget(self, f):
287 'forget a file'
294 'forget a file'
288 self._dirty = True
295 self._dirty = True
289 try:
296 try:
290 self._changepath(f, '?')
297 self._changepath(f, '?')
291 del self._map[f]
298 del self._map[f]
292 except KeyError:
299 except KeyError:
293 self._ui.warn(_("not in dirstate: %s\n") % f)
300 self._ui.warn(_("not in dirstate: %s\n") % f)
294
301
295 def clear(self):
302 def clear(self):
296 self._map = {}
303 self._map = {}
297 if "_dirs" in self.__dict__:
304 if "_dirs" in self.__dict__:
298 delattr(self, "_dirs");
305 delattr(self, "_dirs");
299 self._copymap = {}
306 self._copymap = {}
300 self._pl = [nullid, nullid]
307 self._pl = [nullid, nullid]
301 self._dirty = True
308 self._dirty = True
302
309
303 def rebuild(self, parent, files):
310 def rebuild(self, parent, files):
304 self.clear()
311 self.clear()
305 for f in files:
312 for f in files:
306 if files.execf(f):
313 if files.execf(f):
307 self._map[f] = ('n', 0777, -1, 0, 0)
314 self._map[f] = ('n', 0777, -1, 0, 0)
308 else:
315 else:
309 self._map[f] = ('n', 0666, -1, 0, 0)
316 self._map[f] = ('n', 0666, -1, 0, 0)
310 self._pl = (parent, nullid)
317 self._pl = (parent, nullid)
311 self._dirty = True
318 self._dirty = True
312
319
313 def write(self):
320 def write(self):
314 if not self._dirty:
321 if not self._dirty:
315 return
322 return
316 cs = cStringIO.StringIO()
323 cs = cStringIO.StringIO()
317 copymap = self._copymap
324 copymap = self._copymap
318 pack = struct.pack
325 pack = struct.pack
319 write = cs.write
326 write = cs.write
320 write("".join(self._pl))
327 write("".join(self._pl))
321 for f, e in self._map.iteritems():
328 for f, e in self._map.iteritems():
322 if f in copymap:
329 if f in copymap:
323 f = "%s\0%s" % (f, copymap[f])
330 f = "%s\0%s" % (f, copymap[f])
324 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
331 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
325 write(e)
332 write(e)
326 write(f)
333 write(f)
327 st = self._opener("dirstate", "w", atomictemp=True)
334 st = self._opener("dirstate", "w", atomictemp=True)
328 st.write(cs.getvalue())
335 st.write(cs.getvalue())
329 st.rename()
336 st.rename()
330 self._dirty = self._dirtypl = False
337 self._dirty = self._dirtypl = False
331
338
332 def _filter(self, files):
339 def _filter(self, files):
333 ret = {}
340 ret = {}
334 unknown = []
341 unknown = []
335
342
336 for x in files:
343 for x in files:
337 if x == '.':
344 if x == '.':
338 return self._map.copy()
345 return self._map.copy()
339 if x not in self._map:
346 if x not in self._map:
340 unknown.append(x)
347 unknown.append(x)
341 else:
348 else:
342 ret[x] = self._map[x]
349 ret[x] = self._map[x]
343
350
344 if not unknown:
351 if not unknown:
345 return ret
352 return ret
346
353
347 b = self._map.keys()
354 b = self._map.keys()
348 b.sort()
355 b.sort()
349 blen = len(b)
356 blen = len(b)
350
357
351 for x in unknown:
358 for x in unknown:
352 bs = bisect.bisect(b, "%s%s" % (x, '/'))
359 bs = bisect.bisect(b, "%s%s" % (x, '/'))
353 while bs < blen:
360 while bs < blen:
354 s = b[bs]
361 s = b[bs]
355 if len(s) > len(x) and s.startswith(x):
362 if len(s) > len(x) and s.startswith(x):
356 ret[s] = self._map[s]
363 ret[s] = self._map[s]
357 else:
364 else:
358 break
365 break
359 bs += 1
366 bs += 1
360 return ret
367 return ret
361
368
362 def _supported(self, f, mode, verbose=False):
369 def _supported(self, f, mode, verbose=False):
363 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
370 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
364 return True
371 return True
365 if verbose:
372 if verbose:
366 kind = 'unknown'
373 kind = 'unknown'
367 if stat.S_ISCHR(mode): kind = _('character device')
374 if stat.S_ISCHR(mode): kind = _('character device')
368 elif stat.S_ISBLK(mode): kind = _('block device')
375 elif stat.S_ISBLK(mode): kind = _('block device')
369 elif stat.S_ISFIFO(mode): kind = _('fifo')
376 elif stat.S_ISFIFO(mode): kind = _('fifo')
370 elif stat.S_ISSOCK(mode): kind = _('socket')
377 elif stat.S_ISSOCK(mode): kind = _('socket')
371 elif stat.S_ISDIR(mode): kind = _('directory')
378 elif stat.S_ISDIR(mode): kind = _('directory')
372 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
379 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
373 % (self.pathto(f), kind))
380 % (self.pathto(f), kind))
374 return False
381 return False
375
382
376 def _dirignore(self, f):
383 def _dirignore(self, f):
377 if self._ignore(f):
384 if self._ignore(f):
378 return True
385 return True
379 for c in strutil.findall(f, '/'):
386 for c in strutil.findall(f, '/'):
380 if self._ignore(f[:c]):
387 if self._ignore(f[:c]):
381 return True
388 return True
382 return False
389 return False
383
390
384 def walk(self, files=None, match=util.always, badmatch=None):
391 def walk(self, files=None, match=util.always, badmatch=None):
385 # filter out the stat
392 # filter out the stat
386 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
393 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
387 yield src, f
394 yield src, f
388
395
389 def statwalk(self, files=None, match=util.always, unknown=True,
396 def statwalk(self, files=None, match=util.always, unknown=True,
390 ignored=False, badmatch=None, directories=False):
397 ignored=False, badmatch=None, directories=False):
391 '''
398 '''
392 walk recursively through the directory tree, finding all files
399 walk recursively through the directory tree, finding all files
393 matched by the match function
400 matched by the match function
394
401
395 results are yielded in a tuple (src, filename, st), where src
402 results are yielded in a tuple (src, filename, st), where src
396 is one of:
403 is one of:
397 'f' the file was found in the directory tree
404 'f' the file was found in the directory tree
398 'd' the file is a directory of the tree
405 'd' the file is a directory of the tree
399 'm' the file was only in the dirstate and not in the tree
406 'm' the file was only in the dirstate and not in the tree
400 'b' file was not found and matched badmatch
407 'b' file was not found and matched badmatch
401
408
402 and st is the stat result if the file was found in the directory.
409 and st is the stat result if the file was found in the directory.
403 '''
410 '''
404
411
405 # walk all files by default
412 # walk all files by default
406 if not files:
413 if not files:
407 files = ['.']
414 files = ['.']
408 dc = self._map.copy()
415 dc = self._map.copy()
409 else:
416 else:
410 files = util.unique(files)
417 files = util.unique(files)
411 dc = self._filter(files)
418 dc = self._filter(files)
412
419
413 def imatch(file_):
420 def imatch(file_):
414 if file_ not in dc and self._ignore(file_):
421 if file_ not in dc and self._ignore(file_):
415 return False
422 return False
416 return match(file_)
423 return match(file_)
417
424
418 # TODO: don't walk unknown directories if unknown and ignored are False
425 # TODO: don't walk unknown directories if unknown and ignored are False
419 ignore = self._ignore
426 ignore = self._ignore
420 dirignore = self._dirignore
427 dirignore = self._dirignore
421 if ignored:
428 if ignored:
422 imatch = match
429 imatch = match
423 ignore = util.never
430 ignore = util.never
424 dirignore = util.never
431 dirignore = util.never
425
432
426 # self._root may end with a path separator when self._root == '/'
433 # self._root may end with a path separator when self._root == '/'
427 common_prefix_len = len(self._root)
434 common_prefix_len = len(self._root)
428 if not util.endswithsep(self._root):
435 if not util.endswithsep(self._root):
429 common_prefix_len += 1
436 common_prefix_len += 1
430
437
431 normpath = util.normpath
438 normpath = util.normpath
432 listdir = osutil.listdir
439 listdir = osutil.listdir
433 lstat = os.lstat
440 lstat = os.lstat
434 bisect_left = bisect.bisect_left
441 bisect_left = bisect.bisect_left
435 isdir = os.path.isdir
442 isdir = os.path.isdir
436 pconvert = util.pconvert
443 pconvert = util.pconvert
437 join = os.path.join
444 join = os.path.join
438 s_isdir = stat.S_ISDIR
445 s_isdir = stat.S_ISDIR
439 supported = self._supported
446 supported = self._supported
440 _join = self._join
447 _join = self._join
441 known = {'.hg': 1}
448 known = {'.hg': 1}
442
449
443 # recursion free walker, faster than os.walk.
450 # recursion free walker, faster than os.walk.
444 def findfiles(s):
451 def findfiles(s):
445 work = [s]
452 work = [s]
446 wadd = work.append
453 wadd = work.append
447 found = []
454 found = []
448 add = found.append
455 add = found.append
449 if directories:
456 if directories:
450 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
457 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
451 while work:
458 while work:
452 top = work.pop()
459 top = work.pop()
453 entries = listdir(top, stat=True)
460 entries = listdir(top, stat=True)
454 # nd is the top of the repository dir tree
461 # nd is the top of the repository dir tree
455 nd = normpath(top[common_prefix_len:])
462 nd = normpath(top[common_prefix_len:])
456 if nd == '.':
463 if nd == '.':
457 nd = ''
464 nd = ''
458 else:
465 else:
459 # do not recurse into a repo contained in this
466 # do not recurse into a repo contained in this
460 # one. use bisect to find .hg directory so speed
467 # one. use bisect to find .hg directory so speed
461 # is good on big directory.
468 # is good on big directory.
462 names = [e[0] for e in entries]
469 names = [e[0] for e in entries]
463 hg = bisect_left(names, '.hg')
470 hg = bisect_left(names, '.hg')
464 if hg < len(names) and names[hg] == '.hg':
471 if hg < len(names) and names[hg] == '.hg':
465 if isdir(join(top, '.hg')):
472 if isdir(join(top, '.hg')):
466 continue
473 continue
467 for f, kind, st in entries:
474 for f, kind, st in entries:
468 np = pconvert(join(nd, f))
475 np = pconvert(join(nd, f))
469 if np in known:
476 if np in known:
470 continue
477 continue
471 known[np] = 1
478 known[np] = 1
472 p = join(top, f)
479 p = join(top, f)
473 # don't trip over symlinks
480 # don't trip over symlinks
474 if kind == stat.S_IFDIR:
481 if kind == stat.S_IFDIR:
475 if not ignore(np):
482 if not ignore(np):
476 wadd(p)
483 wadd(p)
477 if directories:
484 if directories:
478 add((np, 'd', st))
485 add((np, 'd', st))
479 if np in dc and match(np):
486 if np in dc and match(np):
480 add((np, 'm', st))
487 add((np, 'm', st))
481 elif imatch(np):
488 elif imatch(np):
482 if supported(np, st.st_mode):
489 if supported(np, st.st_mode):
483 add((np, 'f', st))
490 add((np, 'f', st))
484 elif np in dc:
491 elif np in dc:
485 add((np, 'm', st))
492 add((np, 'm', st))
486 found.sort()
493 found.sort()
487 return found
494 return found
488
495
489 # step one, find all files that match our criteria
496 # step one, find all files that match our criteria
490 files.sort()
497 files.sort()
491 for ff in files:
498 for ff in files:
492 nf = normpath(ff)
499 nf = normpath(ff)
493 f = _join(ff)
500 f = _join(ff)
494 try:
501 try:
495 st = lstat(f)
502 st = lstat(f)
496 except OSError, inst:
503 except OSError, inst:
497 found = False
504 found = False
498 for fn in dc:
505 for fn in dc:
499 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
506 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
500 found = True
507 found = True
501 break
508 break
502 if not found:
509 if not found:
503 if inst.errno != errno.ENOENT or not badmatch:
510 if inst.errno != errno.ENOENT or not badmatch:
504 self._ui.warn('%s: %s\n' %
511 self._ui.warn('%s: %s\n' %
505 (self.pathto(ff), inst.strerror))
512 (self.pathto(ff), inst.strerror))
506 elif badmatch and badmatch(ff) and imatch(nf):
513 elif badmatch and badmatch(ff) and imatch(nf):
507 yield 'b', ff, None
514 yield 'b', ff, None
508 continue
515 continue
509 if s_isdir(st.st_mode):
516 if s_isdir(st.st_mode):
510 if not dirignore(nf):
517 if not dirignore(nf):
511 for f, src, st in findfiles(f):
518 for f, src, st in findfiles(f):
512 yield src, f, st
519 yield src, f, st
513 else:
520 else:
514 if nf in known:
521 if nf in known:
515 continue
522 continue
516 known[nf] = 1
523 known[nf] = 1
517 if match(nf):
524 if match(nf):
518 if supported(ff, st.st_mode, verbose=True):
525 if supported(ff, st.st_mode, verbose=True):
519 yield 'f', nf, st
526 yield 'f', nf, st
520 elif ff in dc:
527 elif ff in dc:
521 yield 'm', nf, st
528 yield 'm', nf, st
522
529
523 # step two run through anything left in the dc hash and yield
530 # step two run through anything left in the dc hash and yield
524 # if we haven't already seen it
531 # if we haven't already seen it
525 ks = dc.keys()
532 ks = dc.keys()
526 ks.sort()
533 ks.sort()
527 for k in ks:
534 for k in ks:
528 if k in known:
535 if k in known:
529 continue
536 continue
530 known[k] = 1
537 known[k] = 1
531 if imatch(k):
538 if imatch(k):
532 yield 'm', k, None
539 yield 'm', k, None
533
540
534 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
541 def status(self, files, match, list_ignored, list_clean, list_unknown=True):
535 lookup, modified, added, unknown, ignored = [], [], [], [], []
542 lookup, modified, added, unknown, ignored = [], [], [], [], []
536 removed, deleted, clean = [], [], []
543 removed, deleted, clean = [], [], []
537
544
538 files = files or []
545 files = files or []
539 _join = self._join
546 _join = self._join
540 lstat = os.lstat
547 lstat = os.lstat
541 cmap = self._copymap
548 cmap = self._copymap
542 dmap = self._map
549 dmap = self._map
543 ladd = lookup.append
550 ladd = lookup.append
544 madd = modified.append
551 madd = modified.append
545 aadd = added.append
552 aadd = added.append
546 uadd = unknown.append
553 uadd = unknown.append
547 iadd = ignored.append
554 iadd = ignored.append
548 radd = removed.append
555 radd = removed.append
549 dadd = deleted.append
556 dadd = deleted.append
550 cadd = clean.append
557 cadd = clean.append
551
558
552 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
559 for src, fn, st in self.statwalk(files, match, unknown=list_unknown,
553 ignored=list_ignored):
560 ignored=list_ignored):
554 if fn in dmap:
561 if fn in dmap:
555 type_, mode, size, time, foo = dmap[fn]
562 type_, mode, size, time, foo = dmap[fn]
556 else:
563 else:
557 if (list_ignored or fn in files) and self._dirignore(fn):
564 if (list_ignored or fn in files) and self._dirignore(fn):
558 if list_ignored:
565 if list_ignored:
559 iadd(fn)
566 iadd(fn)
560 elif list_unknown:
567 elif list_unknown:
561 uadd(fn)
568 uadd(fn)
562 continue
569 continue
563 if src == 'm':
570 if src == 'm':
564 nonexistent = True
571 nonexistent = True
565 if not st:
572 if not st:
566 try:
573 try:
567 st = lstat(_join(fn))
574 st = lstat(_join(fn))
568 except OSError, inst:
575 except OSError, inst:
569 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
576 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
570 raise
577 raise
571 st = None
578 st = None
572 # We need to re-check that it is a valid file
579 # We need to re-check that it is a valid file
573 if st and self._supported(fn, st.st_mode):
580 if st and self._supported(fn, st.st_mode):
574 nonexistent = False
581 nonexistent = False
575 # XXX: what to do with file no longer present in the fs
582 # XXX: what to do with file no longer present in the fs
576 # who are not removed in the dirstate ?
583 # who are not removed in the dirstate ?
577 if nonexistent and type_ in "nma":
584 if nonexistent and type_ in "nma":
578 dadd(fn)
585 dadd(fn)
579 continue
586 continue
580 # check the common case first
587 # check the common case first
581 if type_ == 'n':
588 if type_ == 'n':
582 if not st:
589 if not st:
583 st = lstat(_join(fn))
590 st = lstat(_join(fn))
584 if (size >= 0 and
591 if (size >= 0 and
585 (size != st.st_size
592 (size != st.st_size
586 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
593 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
587 or size == -2
594 or size == -2
588 or fn in self._copymap):
595 or fn in self._copymap):
589 madd(fn)
596 madd(fn)
590 elif time != int(st.st_mtime):
597 elif time != int(st.st_mtime):
591 ladd(fn)
598 ladd(fn)
592 elif list_clean:
599 elif list_clean:
593 cadd(fn)
600 cadd(fn)
594 elif type_ == 'm':
601 elif type_ == 'm':
595 madd(fn)
602 madd(fn)
596 elif type_ == 'a':
603 elif type_ == 'a':
597 aadd(fn)
604 aadd(fn)
598 elif type_ == 'r':
605 elif type_ == 'r':
599 radd(fn)
606 radd(fn)
600
607
601 return (lookup, modified, added, removed, deleted, unknown, ignored,
608 return (lookup, modified, added, removed, deleted, unknown, ignored,
602 clean)
609 clean)
General Comments 0
You need to be logged in to leave comments. Login now