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