##// END OF EJS Templates
dirstate: two more stat -> lstat changes
mpm@selenic.com -
r1230:6eac821c default
parent child Browse files
Show More
@@ -1,376 +1,376 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.lstat(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 file = util.pconvert(file)
222 if file not in dc and self.ignore(file):
222 if file not in dc and self.ignore(file):
223 return False
223 return False
224 return match(file)
224 return match(file)
225
225
226 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
226 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
227
227
228 # walk recursively through the directory tree, finding all files
228 # walk recursively through the directory tree, finding all files
229 # matched by the statmatch function
229 # matched by the statmatch function
230 #
230 #
231 # 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:
232 # 'f' the file was found in the directory tree
232 # 'f' the file was found in the directory tree
233 # '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
234 #
234 #
235 # 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
236 # 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.
237 #
237 #
238 def walkhelper(self, files, statmatch, dc):
238 def walkhelper(self, files, statmatch, dc):
239 # recursion free walker, faster than os.walk.
239 # recursion free walker, faster than os.walk.
240 def findfiles(s):
240 def findfiles(s):
241 retfiles = []
241 retfiles = []
242 work = [s]
242 work = [s]
243 while work:
243 while work:
244 top = work.pop()
244 top = work.pop()
245 names = os.listdir(top)
245 names = os.listdir(top)
246 names.sort()
246 names.sort()
247 # nd is the top of the repository dir tree
247 # nd is the top of the repository dir tree
248 nd = util.normpath(top[len(self.root) + 1:])
248 nd = util.normpath(top[len(self.root) + 1:])
249 if nd == '.': nd = ''
249 if nd == '.': nd = ''
250 for f in names:
250 for f in names:
251 np = os.path.join(nd, f)
251 np = os.path.join(nd, f)
252 if seen(np):
252 if seen(np):
253 continue
253 continue
254 p = os.path.join(top, f)
254 p = os.path.join(top, f)
255 # don't trip over symlinks
255 # don't trip over symlinks
256 st = os.lstat(p)
256 st = os.lstat(p)
257 if stat.S_ISDIR(st.st_mode):
257 if stat.S_ISDIR(st.st_mode):
258 ds = os.path.join(nd, f +'/')
258 ds = os.path.join(nd, f +'/')
259 if statmatch(ds, st):
259 if statmatch(ds, st):
260 work.append(p)
260 work.append(p)
261 else:
261 else:
262 if statmatch(np, st):
262 if statmatch(np, st):
263 yield np
263 yield np
264
264
265 known = {'.hg': 1}
265 known = {'.hg': 1}
266 def seen(fn):
266 def seen(fn):
267 if fn in known: return True
267 if fn in known: return True
268 known[fn] = 1
268 known[fn] = 1
269
269
270 # step one, find all files that match our criteria
270 # step one, find all files that match our criteria
271 files.sort()
271 files.sort()
272 for ff in util.unique(files):
272 for ff in util.unique(files):
273 f = os.path.join(self.root, ff)
273 f = os.path.join(self.root, ff)
274 try:
274 try:
275 st = os.stat(f)
275 st = os.lstat(f)
276 except OSError, inst:
276 except OSError, inst:
277 if ff not in dc: self.ui.warn('%s: %s\n' % (
277 if ff not in dc: self.ui.warn('%s: %s\n' % (
278 util.pathto(self.getcwd(), ff),
278 util.pathto(self.getcwd(), ff),
279 inst.strerror))
279 inst.strerror))
280 continue
280 continue
281 if stat.S_ISDIR(st.st_mode):
281 if stat.S_ISDIR(st.st_mode):
282 sorted = [ x for x in findfiles(f) ]
282 sorted = [ x for x in findfiles(f) ]
283 sorted.sort()
283 sorted.sort()
284 for fl in sorted:
284 for fl in sorted:
285 yield 'f', fl
285 yield 'f', fl
286 elif stat.S_ISREG(st.st_mode):
286 elif stat.S_ISREG(st.st_mode):
287 ff = util.normpath(ff)
287 ff = util.normpath(ff)
288 if seen(ff):
288 if seen(ff):
289 continue
289 continue
290 found = False
290 found = False
291 self.blockignore = True
291 self.blockignore = True
292 if statmatch(ff, st):
292 if statmatch(ff, st):
293 found = True
293 found = True
294 self.blockignore = False
294 self.blockignore = False
295 if found:
295 if found:
296 yield 'f', ff
296 yield 'f', ff
297 else:
297 else:
298 kind = 'unknown'
298 kind = 'unknown'
299 if stat.S_ISCHR(st.st_mode): kind = 'character device'
299 if stat.S_ISCHR(st.st_mode): kind = 'character device'
300 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
300 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
301 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
301 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
302 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
302 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
303 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
303 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
304 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
304 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
305 util.pathto(self.getcwd(), ff),
305 util.pathto(self.getcwd(), ff),
306 kind))
306 kind))
307
307
308 # step two run through anything left in the dc hash and yield
308 # step two run through anything left in the dc hash and yield
309 # if we haven't already seen it
309 # if we haven't already seen it
310 ks = dc.keys()
310 ks = dc.keys()
311 ks.sort()
311 ks.sort()
312 for k in ks:
312 for k in ks:
313 if not seen(k) and (statmatch(k, None)):
313 if not seen(k) and (statmatch(k, None)):
314 yield 'm', k
314 yield 'm', k
315
315
316 def changes(self, files=None, match=util.always):
316 def changes(self, files=None, match=util.always):
317 self.read()
317 self.read()
318 if not files:
318 if not files:
319 files = [self.root]
319 files = [self.root]
320 dc = self.map.copy()
320 dc = self.map.copy()
321 else:
321 else:
322 dc = self.filterfiles(files)
322 dc = self.filterfiles(files)
323 lookup, modified, added, unknown = [], [], [], []
323 lookup, modified, added, unknown = [], [], [], []
324 removed, deleted = [], []
324 removed, deleted = [], []
325
325
326 # statmatch function to eliminate entries from the dirstate copy
326 # statmatch function to eliminate entries from the dirstate copy
327 # and put files into the appropriate array. This gets passed
327 # and put files into the appropriate array. This gets passed
328 # to the walking code
328 # to the walking code
329 def statmatch(fn, s):
329 def statmatch(fn, s):
330 fn = util.pconvert(fn)
330 fn = util.pconvert(fn)
331 def checkappend(l, fn):
331 def checkappend(l, fn):
332 if match is util.always or match(fn):
332 if match is util.always or match(fn):
333 l.append(fn)
333 l.append(fn)
334
334
335 if not s or stat.S_ISDIR(s.st_mode):
335 if not s or stat.S_ISDIR(s.st_mode):
336 return self.ignore(fn) and False or match(fn)
336 return self.ignore(fn) and False or match(fn)
337
337
338 if not stat.S_ISREG(s.st_mode):
338 if not stat.S_ISREG(s.st_mode):
339 return False
339 return False
340 c = dc.pop(fn, None)
340 c = dc.pop(fn, None)
341 if c:
341 if c:
342 type, mode, size, time = c
342 type, mode, size, time = c
343 # check the common case first
343 # check the common case first
344 if type == 'n':
344 if type == 'n':
345 if size != s.st_size or (mode ^ s.st_mode) & 0100:
345 if size != s.st_size or (mode ^ s.st_mode) & 0100:
346 checkappend(modified, fn)
346 checkappend(modified, fn)
347 elif time != s.st_mtime:
347 elif time != s.st_mtime:
348 checkappend(lookup, fn)
348 checkappend(lookup, fn)
349 elif type == 'm':
349 elif type == 'm':
350 checkappend(modified, fn)
350 checkappend(modified, fn)
351 elif type == 'a':
351 elif type == 'a':
352 checkappend(added, fn)
352 checkappend(added, fn)
353 elif type == 'r':
353 elif type == 'r':
354 checkappend(unknown, fn)
354 checkappend(unknown, fn)
355 else:
355 else:
356 if not self.ignore(fn) and match(fn):
356 if not self.ignore(fn) and match(fn):
357 unknown.append(fn)
357 unknown.append(fn)
358 # return false because we've already handled all cases above.
358 # return false because we've already handled all cases above.
359 # there's no need for the walking code to process the file
359 # there's no need for the walking code to process the file
360 # any further.
360 # any further.
361 return False
361 return False
362
362
363 # because our statmatch always returns false, self.walk will only
363 # because our statmatch always returns false, self.walk will only
364 # return files in the dirstate map that are not present in the FS.
364 # return files in the dirstate map that are not present in the FS.
365 # But, we still need to iterate through the results to force the
365 # But, we still need to iterate through the results to force the
366 # walk to complete
366 # walk to complete
367 for src, fn in self.walkhelper(files, statmatch, dc):
367 for src, fn in self.walkhelper(files, statmatch, dc):
368 pass
368 pass
369
369
370 # anything left in dc didn't exist in the filesystem
370 # anything left in dc didn't exist in the filesystem
371 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
371 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
372 if c[0] == 'r':
372 if c[0] == 'r':
373 removed.append(fn)
373 removed.append(fn)
374 else:
374 else:
375 deleted.append(fn)
375 deleted.append(fn)
376 return (lookup, modified, added, removed + deleted, unknown)
376 return (lookup, modified, added, removed + deleted, unknown)
General Comments 0
You need to be logged in to leave comments. Login now