##// END OF EJS Templates
dirstate.read: make 15% faster....
Vadim Gelfer -
r2427:150cde10 default
parent child Browse files
Show More
@@ -1,478 +1,488
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005 Matt Mackall <mpm@selenic.com>
4 Copyright 2005 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 import struct, os
10 import struct, os
11 from node import *
11 from node import *
12 from i18n import gettext as _
12 from i18n import gettext as _
13 from demandload import *
13 from demandload import *
14 demandload(globals(), "time bisect stat util re errno")
14 demandload(globals(), "time bisect stat util re errno")
15
15
16 class dirstate(object):
16 class dirstate(object):
17 format = ">cllll"
17 format = ">cllll"
18
18
19 def __init__(self, opener, ui, root):
19 def __init__(self, opener, ui, root):
20 self.opener = opener
20 self.opener = opener
21 self.root = root
21 self.root = root
22 self.dirty = 0
22 self.dirty = 0
23 self.ui = ui
23 self.ui = ui
24 self.map = None
24 self.map = None
25 self.pl = None
25 self.pl = None
26 self.copies = {}
26 self.copies = {}
27 self.ignorefunc = None
27 self.ignorefunc = None
28 self.blockignore = False
28 self.blockignore = False
29
29
30 def wjoin(self, f):
30 def wjoin(self, f):
31 return os.path.join(self.root, f)
31 return os.path.join(self.root, f)
32
32
33 def getcwd(self):
33 def getcwd(self):
34 cwd = os.getcwd()
34 cwd = os.getcwd()
35 if cwd == self.root: return ''
35 if cwd == self.root: return ''
36 return cwd[len(self.root) + 1:]
36 return cwd[len(self.root) + 1:]
37
37
38 def hgignore(self):
38 def hgignore(self):
39 '''return the contents of .hgignore files as a list of patterns.
39 '''return the contents of .hgignore files as a list of patterns.
40
40
41 the files parsed for patterns include:
41 the files parsed for patterns include:
42 .hgignore in the repository root
42 .hgignore in the repository root
43 any additional files specified in the [ui] section of ~/.hgrc
43 any additional files specified in the [ui] section of ~/.hgrc
44
44
45 trailing white space is dropped.
45 trailing white space is dropped.
46 the escape character is backslash.
46 the escape character is backslash.
47 comments start with #.
47 comments start with #.
48 empty lines are skipped.
48 empty lines are skipped.
49
49
50 lines can be of the following formats:
50 lines can be of the following formats:
51
51
52 syntax: regexp # defaults following lines to non-rooted regexps
52 syntax: regexp # defaults following lines to non-rooted regexps
53 syntax: glob # defaults following lines to non-rooted globs
53 syntax: glob # defaults following lines to non-rooted globs
54 re:pattern # non-rooted regular expression
54 re:pattern # non-rooted regular expression
55 glob:pattern # non-rooted glob
55 glob:pattern # non-rooted glob
56 pattern # pattern of the current default type'''
56 pattern # pattern of the current default type'''
57 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
57 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
58 def parselines(fp):
58 def parselines(fp):
59 for line in fp:
59 for line in fp:
60 escape = False
60 escape = False
61 for i in xrange(len(line)):
61 for i in xrange(len(line)):
62 if escape: escape = False
62 if escape: escape = False
63 elif line[i] == '\\': escape = True
63 elif line[i] == '\\': escape = True
64 elif line[i] == '#': break
64 elif line[i] == '#': break
65 line = line[:i].rstrip()
65 line = line[:i].rstrip()
66 if line: yield line
66 if line: yield line
67 repoignore = self.wjoin('.hgignore')
67 repoignore = self.wjoin('.hgignore')
68 files = [repoignore]
68 files = [repoignore]
69 files.extend(self.ui.hgignorefiles())
69 files.extend(self.ui.hgignorefiles())
70 pats = {}
70 pats = {}
71 for f in files:
71 for f in files:
72 try:
72 try:
73 pats[f] = []
73 pats[f] = []
74 fp = open(f)
74 fp = open(f)
75 syntax = 'relre:'
75 syntax = 'relre:'
76 for line in parselines(fp):
76 for line in parselines(fp):
77 if line.startswith('syntax:'):
77 if line.startswith('syntax:'):
78 s = line[7:].strip()
78 s = line[7:].strip()
79 try:
79 try:
80 syntax = syntaxes[s]
80 syntax = syntaxes[s]
81 except KeyError:
81 except KeyError:
82 self.ui.warn(_("%s: ignoring invalid "
82 self.ui.warn(_("%s: ignoring invalid "
83 "syntax '%s'\n") % (f, s))
83 "syntax '%s'\n") % (f, s))
84 continue
84 continue
85 pat = syntax + line
85 pat = syntax + line
86 for s in syntaxes.values():
86 for s in syntaxes.values():
87 if line.startswith(s):
87 if line.startswith(s):
88 pat = line
88 pat = line
89 break
89 break
90 pats[f].append(pat)
90 pats[f].append(pat)
91 except IOError, inst:
91 except IOError, inst:
92 if f != repoignore:
92 if f != repoignore:
93 self.ui.warn(_("skipping unreadable ignore file"
93 self.ui.warn(_("skipping unreadable ignore file"
94 " '%s': %s\n") % (f, inst.strerror))
94 " '%s': %s\n") % (f, inst.strerror))
95 return pats
95 return pats
96
96
97 def ignore(self, fn):
97 def ignore(self, fn):
98 '''default match function used by dirstate and
98 '''default match function used by dirstate and
99 localrepository. this honours the repository .hgignore file
99 localrepository. this honours the repository .hgignore file
100 and any other files specified in the [ui] section of .hgrc.'''
100 and any other files specified in the [ui] section of .hgrc.'''
101 if self.blockignore:
101 if self.blockignore:
102 return False
102 return False
103 if not self.ignorefunc:
103 if not self.ignorefunc:
104 ignore = self.hgignore()
104 ignore = self.hgignore()
105 allpats = []
105 allpats = []
106 [allpats.extend(patlist) for patlist in ignore.values()]
106 [allpats.extend(patlist) for patlist in ignore.values()]
107 if allpats:
107 if allpats:
108 try:
108 try:
109 files, self.ignorefunc, anypats = (
109 files, self.ignorefunc, anypats = (
110 util.matcher(self.root, inc=allpats, src='.hgignore'))
110 util.matcher(self.root, inc=allpats, src='.hgignore'))
111 except util.Abort:
111 except util.Abort:
112 # Re-raise an exception where the src is the right file
112 # Re-raise an exception where the src is the right file
113 for f, patlist in ignore.items():
113 for f, patlist in ignore.items():
114 files, self.ignorefunc, anypats = (
114 files, self.ignorefunc, anypats = (
115 util.matcher(self.root, inc=patlist, src=f))
115 util.matcher(self.root, inc=patlist, src=f))
116 else:
116 else:
117 self.ignorefunc = util.never
117 self.ignorefunc = util.never
118 return self.ignorefunc(fn)
118 return self.ignorefunc(fn)
119
119
120 def __del__(self):
120 def __del__(self):
121 if self.dirty:
121 if self.dirty:
122 self.write()
122 self.write()
123
123
124 def __getitem__(self, key):
124 def __getitem__(self, key):
125 try:
125 try:
126 return self.map[key]
126 return self.map[key]
127 except TypeError:
127 except TypeError:
128 self.lazyread()
128 self.lazyread()
129 return self[key]
129 return self[key]
130
130
131 def __contains__(self, key):
131 def __contains__(self, key):
132 self.lazyread()
132 self.lazyread()
133 return key in self.map
133 return key in self.map
134
134
135 def parents(self):
135 def parents(self):
136 self.lazyread()
136 self.lazyread()
137 return self.pl
137 return self.pl
138
138
139 def markdirty(self):
139 def markdirty(self):
140 if not self.dirty:
140 if not self.dirty:
141 self.dirty = 1
141 self.dirty = 1
142
142
143 def setparents(self, p1, p2=nullid):
143 def setparents(self, p1, p2=nullid):
144 self.lazyread()
144 self.lazyread()
145 self.markdirty()
145 self.markdirty()
146 self.pl = p1, p2
146 self.pl = p1, p2
147
147
148 def state(self, key):
148 def state(self, key):
149 try:
149 try:
150 return self[key][0]
150 return self[key][0]
151 except KeyError:
151 except KeyError:
152 return "?"
152 return "?"
153
153
154 def lazyread(self):
154 def lazyread(self):
155 if self.map is None:
155 if self.map is None:
156 self.read()
156 self.read()
157
157
158 def read(self):
158 def parse(self, st):
159 self.map = {}
160 self.pl = [nullid, nullid]
161 try:
162 st = self.opener("dirstate").read()
163 if not st: return
164 except: return
165
166 self.pl = [st[:20], st[20: 40]]
159 self.pl = [st[:20], st[20: 40]]
167
160
161 # deref fields so they will be local in loop
162 map = self.map
163 copies = self.copies
164 format = self.format
165 unpack = struct.unpack
166
168 pos = 40
167 pos = 40
169 e_size = struct.calcsize(self.format)
168 e_size = struct.calcsize(format)
169
170 while pos < len(st):
170 while pos < len(st):
171 newpos = pos + e_size
171 newpos = pos + e_size
172 e = struct.unpack(self.format, st[pos:newpos])
172 e = unpack(format, st[pos:newpos])
173 l = e[4]
173 l = e[4]
174 pos = newpos
174 pos = newpos
175 newpos = pos + l
175 newpos = pos + l
176 f = st[pos:newpos]
176 f = st[pos:newpos]
177 if '\0' in f:
177 if '\0' in f:
178 f, c = f.split('\0')
178 f, c = f.split('\0')
179 self.copies[f] = c
179 copies[f] = c
180 self.map[f] = e[:4]
180 map[f] = e[:4]
181 pos = newpos
181 pos = newpos
182
182
183 def read(self):
184 self.map = {}
185 self.pl = [nullid, nullid]
186 try:
187 st = self.opener("dirstate").read()
188 if st:
189 self.parse(st)
190 except IOError, err:
191 if err.errno != errno.ENOENT: raise
192
183 def copy(self, source, dest):
193 def copy(self, source, dest):
184 self.lazyread()
194 self.lazyread()
185 self.markdirty()
195 self.markdirty()
186 self.copies[dest] = source
196 self.copies[dest] = source
187
197
188 def copied(self, file):
198 def copied(self, file):
189 return self.copies.get(file, None)
199 return self.copies.get(file, None)
190
200
191 def update(self, files, state, **kw):
201 def update(self, files, state, **kw):
192 ''' current states:
202 ''' current states:
193 n normal
203 n normal
194 m needs merging
204 m needs merging
195 r marked for removal
205 r marked for removal
196 a marked for addition'''
206 a marked for addition'''
197
207
198 if not files: return
208 if not files: return
199 self.lazyread()
209 self.lazyread()
200 self.markdirty()
210 self.markdirty()
201 for f in files:
211 for f in files:
202 if state == "r":
212 if state == "r":
203 self.map[f] = ('r', 0, 0, 0)
213 self.map[f] = ('r', 0, 0, 0)
204 else:
214 else:
205 s = os.lstat(self.wjoin(f))
215 s = os.lstat(self.wjoin(f))
206 st_size = kw.get('st_size', s.st_size)
216 st_size = kw.get('st_size', s.st_size)
207 st_mtime = kw.get('st_mtime', s.st_mtime)
217 st_mtime = kw.get('st_mtime', s.st_mtime)
208 self.map[f] = (state, s.st_mode, st_size, st_mtime)
218 self.map[f] = (state, s.st_mode, st_size, st_mtime)
209 if self.copies.has_key(f):
219 if self.copies.has_key(f):
210 del self.copies[f]
220 del self.copies[f]
211
221
212 def forget(self, files):
222 def forget(self, files):
213 if not files: return
223 if not files: return
214 self.lazyread()
224 self.lazyread()
215 self.markdirty()
225 self.markdirty()
216 for f in files:
226 for f in files:
217 try:
227 try:
218 del self.map[f]
228 del self.map[f]
219 except KeyError:
229 except KeyError:
220 self.ui.warn(_("not in dirstate: %s!\n") % f)
230 self.ui.warn(_("not in dirstate: %s!\n") % f)
221 pass
231 pass
222
232
223 def clear(self):
233 def clear(self):
224 self.map = {}
234 self.map = {}
225 self.copies = {}
235 self.copies = {}
226 self.markdirty()
236 self.markdirty()
227
237
228 def rebuild(self, parent, files):
238 def rebuild(self, parent, files):
229 self.clear()
239 self.clear()
230 umask = os.umask(0)
240 umask = os.umask(0)
231 os.umask(umask)
241 os.umask(umask)
232 for f, mode in files:
242 for f, mode in files:
233 if mode:
243 if mode:
234 self.map[f] = ('n', ~umask, -1, 0)
244 self.map[f] = ('n', ~umask, -1, 0)
235 else:
245 else:
236 self.map[f] = ('n', ~umask & 0666, -1, 0)
246 self.map[f] = ('n', ~umask & 0666, -1, 0)
237 self.pl = (parent, nullid)
247 self.pl = (parent, nullid)
238 self.markdirty()
248 self.markdirty()
239
249
240 def write(self):
250 def write(self):
241 if not self.dirty:
251 if not self.dirty:
242 return
252 return
243 st = self.opener("dirstate", "w", atomic=True)
253 st = self.opener("dirstate", "w", atomic=True)
244 st.write("".join(self.pl))
254 st.write("".join(self.pl))
245 for f, e in self.map.items():
255 for f, e in self.map.items():
246 c = self.copied(f)
256 c = self.copied(f)
247 if c:
257 if c:
248 f = f + "\0" + c
258 f = f + "\0" + c
249 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
259 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
250 st.write(e + f)
260 st.write(e + f)
251 self.dirty = 0
261 self.dirty = 0
252
262
253 def filterfiles(self, files):
263 def filterfiles(self, files):
254 ret = {}
264 ret = {}
255 unknown = []
265 unknown = []
256
266
257 for x in files:
267 for x in files:
258 if x == '.':
268 if x == '.':
259 return self.map.copy()
269 return self.map.copy()
260 if x not in self.map:
270 if x not in self.map:
261 unknown.append(x)
271 unknown.append(x)
262 else:
272 else:
263 ret[x] = self.map[x]
273 ret[x] = self.map[x]
264
274
265 if not unknown:
275 if not unknown:
266 return ret
276 return ret
267
277
268 b = self.map.keys()
278 b = self.map.keys()
269 b.sort()
279 b.sort()
270 blen = len(b)
280 blen = len(b)
271
281
272 for x in unknown:
282 for x in unknown:
273 bs = bisect.bisect(b, x)
283 bs = bisect.bisect(b, x)
274 if bs != 0 and b[bs-1] == x:
284 if bs != 0 and b[bs-1] == x:
275 ret[x] = self.map[x]
285 ret[x] = self.map[x]
276 continue
286 continue
277 while bs < blen:
287 while bs < blen:
278 s = b[bs]
288 s = b[bs]
279 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
289 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
280 ret[s] = self.map[s]
290 ret[s] = self.map[s]
281 else:
291 else:
282 break
292 break
283 bs += 1
293 bs += 1
284 return ret
294 return ret
285
295
286 def supported_type(self, f, st, verbose=False):
296 def supported_type(self, f, st, verbose=False):
287 if stat.S_ISREG(st.st_mode):
297 if stat.S_ISREG(st.st_mode):
288 return True
298 return True
289 if verbose:
299 if verbose:
290 kind = 'unknown'
300 kind = 'unknown'
291 if stat.S_ISCHR(st.st_mode): kind = _('character device')
301 if stat.S_ISCHR(st.st_mode): kind = _('character device')
292 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
302 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
293 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
303 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
294 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
304 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
295 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
305 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
296 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
306 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
297 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
307 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
298 util.pathto(self.getcwd(), f),
308 util.pathto(self.getcwd(), f),
299 kind))
309 kind))
300 return False
310 return False
301
311
302 def statwalk(self, files=None, match=util.always, dc=None, ignored=False,
312 def statwalk(self, files=None, match=util.always, dc=None, ignored=False,
303 badmatch=None):
313 badmatch=None):
304 self.lazyread()
314 self.lazyread()
305
315
306 # walk all files by default
316 # walk all files by default
307 if not files:
317 if not files:
308 files = [self.root]
318 files = [self.root]
309 if not dc:
319 if not dc:
310 dc = self.map.copy()
320 dc = self.map.copy()
311 elif not dc:
321 elif not dc:
312 dc = self.filterfiles(files)
322 dc = self.filterfiles(files)
313
323
314 def statmatch(file_, stat):
324 def statmatch(file_, stat):
315 file_ = util.pconvert(file_)
325 file_ = util.pconvert(file_)
316 if not ignored and file_ not in dc and self.ignore(file_):
326 if not ignored and file_ not in dc and self.ignore(file_):
317 return False
327 return False
318 return match(file_)
328 return match(file_)
319
329
320 return self.walkhelper(files=files, statmatch=statmatch, dc=dc,
330 return self.walkhelper(files=files, statmatch=statmatch, dc=dc,
321 badmatch=badmatch)
331 badmatch=badmatch)
322
332
323 def walk(self, files=None, match=util.always, dc=None, badmatch=None):
333 def walk(self, files=None, match=util.always, dc=None, badmatch=None):
324 # filter out the stat
334 # filter out the stat
325 for src, f, st in self.statwalk(files, match, dc, badmatch=badmatch):
335 for src, f, st in self.statwalk(files, match, dc, badmatch=badmatch):
326 yield src, f
336 yield src, f
327
337
328 # walk recursively through the directory tree, finding all files
338 # walk recursively through the directory tree, finding all files
329 # matched by the statmatch function
339 # matched by the statmatch function
330 #
340 #
331 # results are yielded in a tuple (src, filename, st), where src
341 # results are yielded in a tuple (src, filename, st), where src
332 # is one of:
342 # is one of:
333 # 'f' the file was found in the directory tree
343 # 'f' the file was found in the directory tree
334 # 'm' the file was only in the dirstate and not in the tree
344 # 'm' the file was only in the dirstate and not in the tree
335 # and st is the stat result if the file was found in the directory.
345 # and st is the stat result if the file was found in the directory.
336 #
346 #
337 # dc is an optional arg for the current dirstate. dc is not modified
347 # dc is an optional arg for the current dirstate. dc is not modified
338 # directly by this function, but might be modified by your statmatch call.
348 # directly by this function, but might be modified by your statmatch call.
339 #
349 #
340 def walkhelper(self, files, statmatch, dc, badmatch=None):
350 def walkhelper(self, files, statmatch, dc, badmatch=None):
341 # recursion free walker, faster than os.walk.
351 # recursion free walker, faster than os.walk.
342 def findfiles(s):
352 def findfiles(s):
343 work = [s]
353 work = [s]
344 while work:
354 while work:
345 top = work.pop()
355 top = work.pop()
346 names = os.listdir(top)
356 names = os.listdir(top)
347 names.sort()
357 names.sort()
348 # nd is the top of the repository dir tree
358 # nd is the top of the repository dir tree
349 nd = util.normpath(top[len(self.root) + 1:])
359 nd = util.normpath(top[len(self.root) + 1:])
350 if nd == '.':
360 if nd == '.':
351 nd = ''
361 nd = ''
352 else:
362 else:
353 # do not recurse into a repo contained in this
363 # do not recurse into a repo contained in this
354 # one. use bisect to find .hg directory so speed
364 # one. use bisect to find .hg directory so speed
355 # is good on big directory.
365 # is good on big directory.
356 hg = bisect.bisect_left(names, '.hg')
366 hg = bisect.bisect_left(names, '.hg')
357 if hg < len(names) and names[hg] == '.hg':
367 if hg < len(names) and names[hg] == '.hg':
358 if os.path.isdir(os.path.join(top, '.hg')):
368 if os.path.isdir(os.path.join(top, '.hg')):
359 continue
369 continue
360 for f in names:
370 for f in names:
361 np = util.pconvert(os.path.join(nd, f))
371 np = util.pconvert(os.path.join(nd, f))
362 if seen(np):
372 if seen(np):
363 continue
373 continue
364 p = os.path.join(top, f)
374 p = os.path.join(top, f)
365 # don't trip over symlinks
375 # don't trip over symlinks
366 st = os.lstat(p)
376 st = os.lstat(p)
367 if stat.S_ISDIR(st.st_mode):
377 if stat.S_ISDIR(st.st_mode):
368 ds = os.path.join(nd, f +'/')
378 ds = os.path.join(nd, f +'/')
369 if statmatch(ds, st):
379 if statmatch(ds, st):
370 work.append(p)
380 work.append(p)
371 if statmatch(np, st) and np in dc:
381 if statmatch(np, st) and np in dc:
372 yield 'm', np, st
382 yield 'm', np, st
373 elif statmatch(np, st):
383 elif statmatch(np, st):
374 if self.supported_type(np, st):
384 if self.supported_type(np, st):
375 yield 'f', np, st
385 yield 'f', np, st
376 elif np in dc:
386 elif np in dc:
377 yield 'm', np, st
387 yield 'm', np, st
378
388
379 known = {'.hg': 1}
389 known = {'.hg': 1}
380 def seen(fn):
390 def seen(fn):
381 if fn in known: return True
391 if fn in known: return True
382 known[fn] = 1
392 known[fn] = 1
383
393
384 # step one, find all files that match our criteria
394 # step one, find all files that match our criteria
385 files.sort()
395 files.sort()
386 for ff in util.unique(files):
396 for ff in util.unique(files):
387 f = self.wjoin(ff)
397 f = self.wjoin(ff)
388 try:
398 try:
389 st = os.lstat(f)
399 st = os.lstat(f)
390 except OSError, inst:
400 except OSError, inst:
391 nf = util.normpath(ff)
401 nf = util.normpath(ff)
392 found = False
402 found = False
393 for fn in dc:
403 for fn in dc:
394 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
404 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
395 found = True
405 found = True
396 break
406 break
397 if not found:
407 if not found:
398 if inst.errno != errno.ENOENT or not badmatch:
408 if inst.errno != errno.ENOENT or not badmatch:
399 self.ui.warn('%s: %s\n' % (
409 self.ui.warn('%s: %s\n' % (
400 util.pathto(self.getcwd(), ff),
410 util.pathto(self.getcwd(), ff),
401 inst.strerror))
411 inst.strerror))
402 elif badmatch and badmatch(ff) and statmatch(ff, None):
412 elif badmatch and badmatch(ff) and statmatch(ff, None):
403 yield 'b', ff, None
413 yield 'b', ff, None
404 continue
414 continue
405 if stat.S_ISDIR(st.st_mode):
415 if stat.S_ISDIR(st.st_mode):
406 cmp1 = (lambda x, y: cmp(x[1], y[1]))
416 cmp1 = (lambda x, y: cmp(x[1], y[1]))
407 sorted_ = [ x for x in findfiles(f) ]
417 sorted_ = [ x for x in findfiles(f) ]
408 sorted_.sort(cmp1)
418 sorted_.sort(cmp1)
409 for e in sorted_:
419 for e in sorted_:
410 yield e
420 yield e
411 else:
421 else:
412 ff = util.normpath(ff)
422 ff = util.normpath(ff)
413 if seen(ff):
423 if seen(ff):
414 continue
424 continue
415 self.blockignore = True
425 self.blockignore = True
416 if statmatch(ff, st):
426 if statmatch(ff, st):
417 if self.supported_type(ff, st, verbose=True):
427 if self.supported_type(ff, st, verbose=True):
418 yield 'f', ff, st
428 yield 'f', ff, st
419 elif ff in dc:
429 elif ff in dc:
420 yield 'm', ff, st
430 yield 'm', ff, st
421 self.blockignore = False
431 self.blockignore = False
422
432
423 # step two run through anything left in the dc hash and yield
433 # step two run through anything left in the dc hash and yield
424 # if we haven't already seen it
434 # if we haven't already seen it
425 ks = dc.keys()
435 ks = dc.keys()
426 ks.sort()
436 ks.sort()
427 for k in ks:
437 for k in ks:
428 if not seen(k) and (statmatch(k, None)):
438 if not seen(k) and (statmatch(k, None)):
429 yield 'm', k, None
439 yield 'm', k, None
430
440
431 def changes(self, files=None, match=util.always, show_ignored=None):
441 def changes(self, files=None, match=util.always, show_ignored=None):
432 lookup, modified, added, unknown, ignored = [], [], [], [], []
442 lookup, modified, added, unknown, ignored = [], [], [], [], []
433 removed, deleted = [], []
443 removed, deleted = [], []
434
444
435 for src, fn, st in self.statwalk(files, match, ignored=show_ignored):
445 for src, fn, st in self.statwalk(files, match, ignored=show_ignored):
436 try:
446 try:
437 type_, mode, size, time = self[fn]
447 type_, mode, size, time = self[fn]
438 except KeyError:
448 except KeyError:
439 if show_ignored and self.ignore(fn):
449 if show_ignored and self.ignore(fn):
440 ignored.append(fn)
450 ignored.append(fn)
441 else:
451 else:
442 unknown.append(fn)
452 unknown.append(fn)
443 continue
453 continue
444 if src == 'm':
454 if src == 'm':
445 nonexistent = True
455 nonexistent = True
446 if not st:
456 if not st:
447 try:
457 try:
448 f = self.wjoin(fn)
458 f = self.wjoin(fn)
449 st = os.lstat(f)
459 st = os.lstat(f)
450 except OSError, inst:
460 except OSError, inst:
451 if inst.errno != errno.ENOENT:
461 if inst.errno != errno.ENOENT:
452 raise
462 raise
453 st = None
463 st = None
454 # We need to re-check that it is a valid file
464 # We need to re-check that it is a valid file
455 if st and self.supported_type(fn, st):
465 if st and self.supported_type(fn, st):
456 nonexistent = False
466 nonexistent = False
457 # XXX: what to do with file no longer present in the fs
467 # XXX: what to do with file no longer present in the fs
458 # who are not removed in the dirstate ?
468 # who are not removed in the dirstate ?
459 if nonexistent and type_ in "nm":
469 if nonexistent and type_ in "nm":
460 deleted.append(fn)
470 deleted.append(fn)
461 continue
471 continue
462 # check the common case first
472 # check the common case first
463 if type_ == 'n':
473 if type_ == 'n':
464 if not st:
474 if not st:
465 st = os.stat(fn)
475 st = os.stat(fn)
466 if size >= 0 and (size != st.st_size
476 if size >= 0 and (size != st.st_size
467 or (mode ^ st.st_mode) & 0100):
477 or (mode ^ st.st_mode) & 0100):
468 modified.append(fn)
478 modified.append(fn)
469 elif time != st.st_mtime:
479 elif time != st.st_mtime:
470 lookup.append(fn)
480 lookup.append(fn)
471 elif type_ == 'm':
481 elif type_ == 'm':
472 modified.append(fn)
482 modified.append(fn)
473 elif type_ == 'a':
483 elif type_ == 'a':
474 added.append(fn)
484 added.append(fn)
475 elif type_ == 'r':
485 elif type_ == 'r':
476 removed.append(fn)
486 removed.append(fn)
477
487
478 return (lookup, modified, added, removed, deleted, unknown, ignored)
488 return (lookup, modified, added, removed, deleted, unknown, ignored)
General Comments 0
You need to be logged in to leave comments. Login now