##// END OF EJS Templates
Fix Windows status problem from new dirstate walk code
mpm@selenic.com -
r1224:cc61d366 default
parent child Browse files
Show More
@@ -1,372 +1,375 b''
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 demandload import *
12 from demandload import *
13 demandload(globals(), "time bisect stat util re")
13 demandload(globals(), "time bisect stat util re")
14
14
15 class dirstate:
15 class dirstate:
16 def __init__(self, opener, ui, root):
16 def __init__(self, opener, ui, root):
17 self.opener = opener
17 self.opener = opener
18 self.root = root
18 self.root = root
19 self.dirty = 0
19 self.dirty = 0
20 self.ui = ui
20 self.ui = ui
21 self.map = None
21 self.map = None
22 self.pl = None
22 self.pl = None
23 self.copies = {}
23 self.copies = {}
24 self.ignorefunc = None
24 self.ignorefunc = None
25 self.blockignore = False
25 self.blockignore = False
26
26
27 def wjoin(self, f):
27 def wjoin(self, f):
28 return os.path.join(self.root, f)
28 return os.path.join(self.root, f)
29
29
30 def getcwd(self):
30 def getcwd(self):
31 cwd = os.getcwd()
31 cwd = os.getcwd()
32 if cwd == self.root: return ''
32 if cwd == self.root: return ''
33 return cwd[len(self.root) + 1:]
33 return cwd[len(self.root) + 1:]
34
34
35 def ignore(self, f):
35 def ignore(self, f):
36 if self.blockignore:
36 if self.blockignore:
37 return False
37 return False
38 if not self.ignorefunc:
38 if not self.ignorefunc:
39 bigpat = []
39 bigpat = []
40 try:
40 try:
41 l = file(self.wjoin(".hgignore"))
41 l = file(self.wjoin(".hgignore"))
42 for pat in l:
42 for pat in l:
43 p = pat.rstrip()
43 p = pat.rstrip()
44 if p:
44 if p:
45 try:
45 try:
46 re.compile(p)
46 re.compile(p)
47 except:
47 except:
48 self.ui.warn("ignoring invalid ignore"
48 self.ui.warn("ignoring invalid ignore"
49 + " regular expression '%s'\n" % p)
49 + " regular expression '%s'\n" % p)
50 else:
50 else:
51 bigpat.append(p)
51 bigpat.append(p)
52 except IOError: pass
52 except IOError: pass
53
53
54 if bigpat:
54 if bigpat:
55 s = "(?:%s)" % (")|(?:".join(bigpat))
55 s = "(?:%s)" % (")|(?:".join(bigpat))
56 r = re.compile(s)
56 r = re.compile(s)
57 self.ignorefunc = r.search
57 self.ignorefunc = r.search
58 else:
58 else:
59 self.ignorefunc = util.never
59 self.ignorefunc = util.never
60
60
61 return self.ignorefunc(f)
61 return self.ignorefunc(f)
62
62
63 def __del__(self):
63 def __del__(self):
64 if self.dirty:
64 if self.dirty:
65 self.write()
65 self.write()
66
66
67 def __getitem__(self, key):
67 def __getitem__(self, key):
68 try:
68 try:
69 return self.map[key]
69 return self.map[key]
70 except TypeError:
70 except TypeError:
71 self.read()
71 self.read()
72 return self[key]
72 return self[key]
73
73
74 def __contains__(self, key):
74 def __contains__(self, key):
75 if not self.map: self.read()
75 if not self.map: self.read()
76 return key in self.map
76 return key in self.map
77
77
78 def parents(self):
78 def parents(self):
79 if not self.pl:
79 if not self.pl:
80 self.read()
80 self.read()
81 return self.pl
81 return self.pl
82
82
83 def markdirty(self):
83 def markdirty(self):
84 if not self.dirty:
84 if not self.dirty:
85 self.dirty = 1
85 self.dirty = 1
86
86
87 def setparents(self, p1, p2=nullid):
87 def setparents(self, p1, p2=nullid):
88 self.markdirty()
88 self.markdirty()
89 self.pl = p1, p2
89 self.pl = p1, p2
90
90
91 def state(self, key):
91 def state(self, key):
92 try:
92 try:
93 return self[key][0]
93 return self[key][0]
94 except KeyError:
94 except KeyError:
95 return "?"
95 return "?"
96
96
97 def read(self):
97 def read(self):
98 if self.map is not None: return self.map
98 if self.map is not None: return self.map
99
99
100 self.map = {}
100 self.map = {}
101 self.pl = [nullid, nullid]
101 self.pl = [nullid, nullid]
102 try:
102 try:
103 st = self.opener("dirstate").read()
103 st = self.opener("dirstate").read()
104 if not st: return
104 if not st: return
105 except: return
105 except: return
106
106
107 self.pl = [st[:20], st[20: 40]]
107 self.pl = [st[:20], st[20: 40]]
108
108
109 pos = 40
109 pos = 40
110 while pos < len(st):
110 while pos < len(st):
111 e = struct.unpack(">cllll", st[pos:pos+17])
111 e = struct.unpack(">cllll", st[pos:pos+17])
112 l = e[4]
112 l = e[4]
113 pos += 17
113 pos += 17
114 f = st[pos:pos + l]
114 f = st[pos:pos + l]
115 if '\0' in f:
115 if '\0' in f:
116 f, c = f.split('\0')
116 f, c = f.split('\0')
117 self.copies[f] = c
117 self.copies[f] = c
118 self.map[f] = e[:4]
118 self.map[f] = e[:4]
119 pos += l
119 pos += l
120
120
121 def copy(self, source, dest):
121 def copy(self, source, dest):
122 self.read()
122 self.read()
123 self.markdirty()
123 self.markdirty()
124 self.copies[dest] = source
124 self.copies[dest] = source
125
125
126 def copied(self, file):
126 def copied(self, file):
127 return self.copies.get(file, None)
127 return self.copies.get(file, None)
128
128
129 def update(self, files, state, **kw):
129 def update(self, files, state, **kw):
130 ''' current states:
130 ''' current states:
131 n normal
131 n normal
132 m needs merging
132 m needs merging
133 r marked for removal
133 r marked for removal
134 a marked for addition'''
134 a marked for addition'''
135
135
136 if not files: return
136 if not files: return
137 self.read()
137 self.read()
138 self.markdirty()
138 self.markdirty()
139 for f in files:
139 for f in files:
140 if state == "r":
140 if state == "r":
141 self.map[f] = ('r', 0, 0, 0)
141 self.map[f] = ('r', 0, 0, 0)
142 else:
142 else:
143 s = os.stat(os.path.join(self.root, f))
143 s = os.stat(os.path.join(self.root, f))
144 st_size = kw.get('st_size', s.st_size)
144 st_size = kw.get('st_size', s.st_size)
145 st_mtime = kw.get('st_mtime', s.st_mtime)
145 st_mtime = kw.get('st_mtime', s.st_mtime)
146 self.map[f] = (state, s.st_mode, st_size, st_mtime)
146 self.map[f] = (state, s.st_mode, st_size, st_mtime)
147 if self.copies.has_key(f):
147 if self.copies.has_key(f):
148 del self.copies[f]
148 del self.copies[f]
149
149
150 def forget(self, files):
150 def forget(self, files):
151 if not files: return
151 if not files: return
152 self.read()
152 self.read()
153 self.markdirty()
153 self.markdirty()
154 for f in files:
154 for f in files:
155 try:
155 try:
156 del self.map[f]
156 del self.map[f]
157 except KeyError:
157 except KeyError:
158 self.ui.warn("not in dirstate: %s!\n" % f)
158 self.ui.warn("not in dirstate: %s!\n" % f)
159 pass
159 pass
160
160
161 def clear(self):
161 def clear(self):
162 self.map = {}
162 self.map = {}
163 self.markdirty()
163 self.markdirty()
164
164
165 def write(self):
165 def write(self):
166 st = self.opener("dirstate", "w")
166 st = self.opener("dirstate", "w")
167 st.write("".join(self.pl))
167 st.write("".join(self.pl))
168 for f, e in self.map.items():
168 for f, e in self.map.items():
169 c = self.copied(f)
169 c = self.copied(f)
170 if c:
170 if c:
171 f = f + "\0" + c
171 f = f + "\0" + c
172 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
172 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
173 st.write(e + f)
173 st.write(e + f)
174 self.dirty = 0
174 self.dirty = 0
175
175
176 def filterfiles(self, files):
176 def filterfiles(self, files):
177 ret = {}
177 ret = {}
178 unknown = []
178 unknown = []
179
179
180 for x in files:
180 for x in files:
181 if x is '.':
181 if x is '.':
182 return self.map.copy()
182 return self.map.copy()
183 if x not in self.map:
183 if x not in self.map:
184 unknown.append(x)
184 unknown.append(x)
185 else:
185 else:
186 ret[x] = self.map[x]
186 ret[x] = self.map[x]
187
187
188 if not unknown:
188 if not unknown:
189 return ret
189 return ret
190
190
191 b = self.map.keys()
191 b = self.map.keys()
192 b.sort()
192 b.sort()
193 blen = len(b)
193 blen = len(b)
194
194
195 for x in unknown:
195 for x in unknown:
196 bs = bisect.bisect(b, x)
196 bs = bisect.bisect(b, x)
197 if bs != 0 and b[bs-1] == x:
197 if bs != 0 and b[bs-1] == x:
198 ret[x] = self.map[x]
198 ret[x] = self.map[x]
199 continue
199 continue
200 while bs < blen:
200 while bs < blen:
201 s = b[bs]
201 s = b[bs]
202 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
202 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
203 ret[s] = self.map[s]
203 ret[s] = self.map[s]
204 else:
204 else:
205 break
205 break
206 bs += 1
206 bs += 1
207 return ret
207 return ret
208
208
209 def walk(self, files=None, match=util.always, dc=None):
209 def walk(self, files=None, match=util.always, dc=None):
210 self.read()
210 self.read()
211
211
212 # walk all files by default
212 # walk all files by default
213 if not files:
213 if not files:
214 files = [self.root]
214 files = [self.root]
215 if not dc:
215 if not dc:
216 dc = self.map.copy()
216 dc = self.map.copy()
217 elif not dc:
217 elif not dc:
218 dc = self.filterfiles(files)
218 dc = self.filterfiles(files)
219
219
220 def statmatch(file, stat):
220 def statmatch(file, stat):
221 file = util.pconvert(file)
221 if file not in dc and self.ignore(file):
222 if file not in dc and self.ignore(file):
222 return False
223 return False
223 return match(file)
224 return match(file)
225
224 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
226 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
225
227
226 # walk recursively through the directory tree, finding all files
228 # walk recursively through the directory tree, finding all files
227 # matched by the statmatch function
229 # matched by the statmatch function
228 #
230 #
229 # results are yielded in a tuple (src, filename), where src is one of:
231 # results are yielded in a tuple (src, filename), where src is one of:
230 # 'f' the file was found in the directory tree
232 # 'f' the file was found in the directory tree
231 # 'm' the file was only in the dirstate and not in the tree
233 # 'm' the file was only in the dirstate and not in the tree
232 #
234 #
233 # dc is an optional arg for the current dirstate. dc is not modified
235 # dc is an optional arg for the current dirstate. dc is not modified
234 # directly by this function, but might be modified by your statmatch call.
236 # directly by this function, but might be modified by your statmatch call.
235 #
237 #
236 def walkhelper(self, files, statmatch, dc):
238 def walkhelper(self, files, statmatch, dc):
237 # recursion free walker, faster than os.walk.
239 # recursion free walker, faster than os.walk.
238 def findfiles(s):
240 def findfiles(s):
239 retfiles = []
241 retfiles = []
240 work = [s]
242 work = [s]
241 while work:
243 while work:
242 top = work.pop()
244 top = work.pop()
243 names = os.listdir(top)
245 names = os.listdir(top)
244 names.sort()
246 names.sort()
245 # nd is the top of the repository dir tree
247 # nd is the top of the repository dir tree
246 nd = util.normpath(top[len(self.root) + 1:])
248 nd = util.normpath(top[len(self.root) + 1:])
247 if nd == '.': nd = ''
249 if nd == '.': nd = ''
248 for f in names:
250 for f in names:
249 np = os.path.join(nd, f)
251 np = os.path.join(nd, f)
250 if seen(np):
252 if seen(np):
251 continue
253 continue
252 p = os.path.join(top, f)
254 p = os.path.join(top, f)
253 st = os.stat(p)
255 st = os.stat(p)
254 if stat.S_ISDIR(st.st_mode):
256 if stat.S_ISDIR(st.st_mode):
255 ds = os.path.join(nd, f +'/')
257 ds = os.path.join(nd, f +'/')
256 if statmatch(ds, st):
258 if statmatch(ds, st):
257 work.append(p)
259 work.append(p)
258 else:
260 else:
259 if statmatch(np, st):
261 if statmatch(np, st):
260 yield np
262 yield np
261
263
262 known = {'.hg': 1}
264 known = {'.hg': 1}
263 def seen(fn):
265 def seen(fn):
264 if fn in known: return True
266 if fn in known: return True
265 known[fn] = 1
267 known[fn] = 1
266
268
267 # step one, find all files that match our criteria
269 # step one, find all files that match our criteria
268 files.sort()
270 files.sort()
269 for ff in util.unique(files):
271 for ff in util.unique(files):
270 f = os.path.join(self.root, ff)
272 f = os.path.join(self.root, ff)
271 try:
273 try:
272 st = os.stat(f)
274 st = os.stat(f)
273 except OSError, inst:
275 except OSError, inst:
274 if ff not in dc: self.ui.warn('%s: %s\n' % (
276 if ff not in dc: self.ui.warn('%s: %s\n' % (
275 util.pathto(self.getcwd(), ff),
277 util.pathto(self.getcwd(), ff),
276 inst.strerror))
278 inst.strerror))
277 continue
279 continue
278 if stat.S_ISDIR(st.st_mode):
280 if stat.S_ISDIR(st.st_mode):
279 sorted = [ x for x in findfiles(f) ]
281 sorted = [ x for x in findfiles(f) ]
280 sorted.sort()
282 sorted.sort()
281 for fl in sorted:
283 for fl in sorted:
282 yield 'f', fl
284 yield 'f', fl
283 elif stat.S_ISREG(st.st_mode):
285 elif stat.S_ISREG(st.st_mode):
284 ff = util.normpath(ff)
286 ff = util.normpath(ff)
285 if seen(ff):
287 if seen(ff):
286 continue
288 continue
287 found = False
289 found = False
288 self.blockignore = True
290 self.blockignore = True
289 if statmatch(ff, st):
291 if statmatch(ff, st):
290 found = True
292 found = True
291 self.blockignore = False
293 self.blockignore = False
292 if found:
294 if found:
293 yield 'f', ff
295 yield 'f', ff
294 else:
296 else:
295 kind = 'unknown'
297 kind = 'unknown'
296 if stat.S_ISCHR(st.st_mode): kind = 'character device'
298 if stat.S_ISCHR(st.st_mode): kind = 'character device'
297 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
299 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
298 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
300 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
299 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
301 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
300 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
302 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
301 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
303 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
302 util.pathto(self.getcwd(), ff),
304 util.pathto(self.getcwd(), ff),
303 kind))
305 kind))
304
306
305 # step two run through anything left in the dc hash and yield
307 # step two run through anything left in the dc hash and yield
306 # if we haven't already seen it
308 # if we haven't already seen it
307 ks = dc.keys()
309 ks = dc.keys()
308 ks.sort()
310 ks.sort()
309 for k in ks:
311 for k in ks:
310 if not seen(k) and (statmatch(k, None)):
312 if not seen(k) and (statmatch(k, None)):
311 yield 'm', k
313 yield 'm', k
312
314
313 def changes(self, files=None, match=util.always):
315 def changes(self, files=None, match=util.always):
314 self.read()
316 self.read()
315 if not files:
317 if not files:
316 files = [self.root]
318 files = [self.root]
317 dc = self.map.copy()
319 dc = self.map.copy()
318 else:
320 else:
319 dc = self.filterfiles(files)
321 dc = self.filterfiles(files)
320 lookup, modified, added, unknown = [], [], [], []
322 lookup, modified, added, unknown = [], [], [], []
321 removed, deleted = [], []
323 removed, deleted = [], []
322
324
323 # statmatch function to eliminate entries from the dirstate copy
325 # statmatch function to eliminate entries from the dirstate copy
324 # and put files into the appropriate array. This gets passed
326 # and put files into the appropriate array. This gets passed
325 # to the walking code
327 # to the walking code
326 def statmatch(fn, s):
328 def statmatch(fn, s):
329 fn = util.pconvert(fn)
327 def checkappend(l, fn):
330 def checkappend(l, fn):
328 if match is util.always or match(fn):
331 if match is util.always or match(fn):
329 l.append(fn)
332 l.append(fn)
330
333
331 if not s or stat.S_ISDIR(s.st_mode):
334 if not s or stat.S_ISDIR(s.st_mode):
332 return self.ignore(fn) and False or match(fn)
335 return self.ignore(fn) and False or match(fn)
333
336
334 if not stat.S_ISREG(s.st_mode):
337 if not stat.S_ISREG(s.st_mode):
335 return False
338 return False
336 c = dc.pop(fn, None)
339 c = dc.pop(fn, None)
337 if c:
340 if c:
338 type, mode, size, time = c
341 type, mode, size, time = c
339 # check the common case first
342 # check the common case first
340 if type == 'n':
343 if type == 'n':
341 if size != s.st_size or (mode ^ s.st_mode) & 0100:
344 if size != s.st_size or (mode ^ s.st_mode) & 0100:
342 checkappend(modified, fn)
345 checkappend(modified, fn)
343 elif time != s.st_mtime:
346 elif time != s.st_mtime:
344 checkappend(lookup, fn)
347 checkappend(lookup, fn)
345 elif type == 'm':
348 elif type == 'm':
346 checkappend(modified, fn)
349 checkappend(modified, fn)
347 elif type == 'a':
350 elif type == 'a':
348 checkappend(added, fn)
351 checkappend(added, fn)
349 elif type == 'r':
352 elif type == 'r':
350 checkappend(unknown, fn)
353 checkappend(unknown, fn)
351 else:
354 else:
352 if not self.ignore(fn) and match(fn):
355 if not self.ignore(fn) and match(fn):
353 unknown.append(fn)
356 unknown.append(fn)
354 # return false because we've already handled all cases above.
357 # return false because we've already handled all cases above.
355 # there's no need for the walking code to process the file
358 # there's no need for the walking code to process the file
356 # any further.
359 # any further.
357 return False
360 return False
358
361
359 # because our statmatch always returns false, self.walk will only
362 # because our statmatch always returns false, self.walk will only
360 # return files in the dirstate map that are not present in the FS.
363 # return files in the dirstate map that are not present in the FS.
361 # But, we still need to iterate through the results to force the
364 # But, we still need to iterate through the results to force the
362 # walk to complete
365 # walk to complete
363 for src, fn in self.walkhelper(files, statmatch, dc):
366 for src, fn in self.walkhelper(files, statmatch, dc):
364 pass
367 pass
365
368
366 # anything left in dc didn't exist in the filesystem
369 # anything left in dc didn't exist in the filesystem
367 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
370 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
368 if c[0] == 'r':
371 if c[0] == 'r':
369 removed.append(fn)
372 removed.append(fn)
370 else:
373 else:
371 deleted.append(fn)
374 deleted.append(fn)
372 return (lookup, modified, added, removed + deleted, unknown)
375 return (lookup, modified, added, removed + deleted, unknown)
General Comments 0
You need to be logged in to leave comments. Login now