##// END OF EJS Templates
dirstate: C parsing extension
Matt Mackall -
r7093:16bafceb default
parent child Browse files
Show More
@@ -1,603 +1,583 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, stat, util, errno, ignore
12 import struct, os, stat, util, errno, ignore
13 import cStringIO, osutil, sys
13 import cStringIO, osutil, sys, parsers
14
14
15 _unknown = ('?', 0, 0, 0)
15 _unknown = ('?', 0, 0, 0)
16 _format = ">cllll"
16 _format = ">cllll"
17
17
18 def _finddirs(path):
18 def _finddirs(path):
19 pos = path.rfind('/')
19 pos = path.rfind('/')
20 while pos != -1:
20 while pos != -1:
21 yield path[:pos]
21 yield path[:pos]
22 pos = path.rfind('/', 0, pos)
22 pos = path.rfind('/', 0, pos)
23
23
24 class dirstate(object):
24 class dirstate(object):
25
25
26 def __init__(self, opener, ui, root):
26 def __init__(self, opener, ui, root):
27 self._opener = opener
27 self._opener = opener
28 self._root = root
28 self._root = root
29 self._rootdir = os.path.join(root, '')
29 self._rootdir = os.path.join(root, '')
30 self._dirty = False
30 self._dirty = False
31 self._dirtypl = False
31 self._dirtypl = False
32 self._ui = ui
32 self._ui = ui
33
33
34 def __getattr__(self, name):
34 def __getattr__(self, name):
35 if name == '_map':
35 if name == '_map':
36 self._read()
36 self._read()
37 return self._map
37 return self._map
38 elif name == '_copymap':
38 elif name == '_copymap':
39 self._read()
39 self._read()
40 return self._copymap
40 return self._copymap
41 elif name == '_foldmap':
41 elif name == '_foldmap':
42 _foldmap = {}
42 _foldmap = {}
43 for name in self._map:
43 for name in self._map:
44 norm = os.path.normcase(name)
44 norm = os.path.normcase(name)
45 _foldmap[norm] = name
45 _foldmap[norm] = name
46 self._foldmap = _foldmap
46 self._foldmap = _foldmap
47 return self._foldmap
47 return self._foldmap
48 elif name == '_branch':
48 elif name == '_branch':
49 try:
49 try:
50 self._branch = (self._opener("branch").read().strip()
50 self._branch = (self._opener("branch").read().strip()
51 or "default")
51 or "default")
52 except IOError:
52 except IOError:
53 self._branch = "default"
53 self._branch = "default"
54 return self._branch
54 return self._branch
55 elif name == '_pl':
55 elif name == '_pl':
56 self._pl = [nullid, nullid]
56 self._pl = [nullid, nullid]
57 try:
57 try:
58 st = self._opener("dirstate").read(40)
58 st = self._opener("dirstate").read(40)
59 if len(st) == 40:
59 if len(st) == 40:
60 self._pl = st[:20], st[20:40]
60 self._pl = st[:20], st[20:40]
61 except IOError, err:
61 except IOError, err:
62 if err.errno != errno.ENOENT: raise
62 if err.errno != errno.ENOENT: raise
63 return self._pl
63 return self._pl
64 elif name == '_dirs':
64 elif name == '_dirs':
65 dirs = {}
65 dirs = {}
66 for f,s in self._map.iteritems():
66 for f,s in self._map.iteritems():
67 if s[0] != 'r':
67 if s[0] != 'r':
68 pos = f.rfind('/')
68 pos = f.rfind('/')
69 while pos != -1:
69 while pos != -1:
70 f = f[:pos]
70 f = f[:pos]
71 if f in dirs:
71 if f in dirs:
72 dirs[f] += 1
72 dirs[f] += 1
73 break
73 break
74 dirs[f] = 1
74 dirs[f] = 1
75 pos = f.rfind('/')
75 pos = f.rfind('/')
76 self._dirs = dirs
76 self._dirs = dirs
77 return self._dirs
77 return self._dirs
78 elif name == '_ignore':
78 elif name == '_ignore':
79 files = [self._join('.hgignore')]
79 files = [self._join('.hgignore')]
80 for name, path in self._ui.configitems("ui"):
80 for name, path in self._ui.configitems("ui"):
81 if name == 'ignore' or name.startswith('ignore.'):
81 if name == 'ignore' or name.startswith('ignore.'):
82 files.append(os.path.expanduser(path))
82 files.append(os.path.expanduser(path))
83 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
83 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
84 return self._ignore
84 return self._ignore
85 elif name == '_slash':
85 elif name == '_slash':
86 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
86 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
87 return self._slash
87 return self._slash
88 elif name == '_checklink':
88 elif name == '_checklink':
89 self._checklink = util.checklink(self._root)
89 self._checklink = util.checklink(self._root)
90 return self._checklink
90 return self._checklink
91 elif name == '_checkexec':
91 elif name == '_checkexec':
92 self._checkexec = util.checkexec(self._root)
92 self._checkexec = util.checkexec(self._root)
93 return self._checkexec
93 return self._checkexec
94 elif name == '_checkcase':
94 elif name == '_checkcase':
95 self._checkcase = not util.checkcase(self._join('.hg'))
95 self._checkcase = not util.checkcase(self._join('.hg'))
96 return self._checkcase
96 return self._checkcase
97 elif name == 'normalize':
97 elif name == 'normalize':
98 if self._checkcase:
98 if self._checkcase:
99 self.normalize = self._normalize
99 self.normalize = self._normalize
100 else:
100 else:
101 self.normalize = lambda x, y=False: x
101 self.normalize = lambda x, y=False: x
102 return self.normalize
102 return self.normalize
103 else:
103 else:
104 raise AttributeError(name)
104 raise AttributeError(name)
105
105
106 def _join(self, f):
106 def _join(self, f):
107 # much faster than os.path.join()
107 # much faster than os.path.join()
108 # it's safe because f is always a relative path
108 # it's safe because f is always a relative path
109 return self._rootdir + f
109 return self._rootdir + f
110
110
111 def flagfunc(self, fallback):
111 def flagfunc(self, fallback):
112 if self._checklink:
112 if self._checklink:
113 if self._checkexec:
113 if self._checkexec:
114 def f(x):
114 def f(x):
115 p = self._join(x)
115 p = self._join(x)
116 if os.path.islink(p):
116 if os.path.islink(p):
117 return 'l'
117 return 'l'
118 if util.is_exec(p):
118 if util.is_exec(p):
119 return 'x'
119 return 'x'
120 return ''
120 return ''
121 return f
121 return f
122 def f(x):
122 def f(x):
123 if os.path.islink(self._join(x)):
123 if os.path.islink(self._join(x)):
124 return 'l'
124 return 'l'
125 if 'x' in fallback(x):
125 if 'x' in fallback(x):
126 return 'x'
126 return 'x'
127 return ''
127 return ''
128 return f
128 return f
129 if self._checkexec:
129 if self._checkexec:
130 def f(x):
130 def f(x):
131 if 'l' in fallback(x):
131 if 'l' in fallback(x):
132 return 'l'
132 return 'l'
133 if util.is_exec(self._join(x)):
133 if util.is_exec(self._join(x)):
134 return 'x'
134 return 'x'
135 return ''
135 return ''
136 return f
136 return f
137 return fallback
137 return fallback
138
138
139 def getcwd(self):
139 def getcwd(self):
140 cwd = os.getcwd()
140 cwd = os.getcwd()
141 if cwd == self._root: return ''
141 if cwd == self._root: return ''
142 # self._root ends with a path separator if self._root is '/' or 'C:\'
142 # self._root ends with a path separator if self._root is '/' or 'C:\'
143 rootsep = self._root
143 rootsep = self._root
144 if not util.endswithsep(rootsep):
144 if not util.endswithsep(rootsep):
145 rootsep += os.sep
145 rootsep += os.sep
146 if cwd.startswith(rootsep):
146 if cwd.startswith(rootsep):
147 return cwd[len(rootsep):]
147 return cwd[len(rootsep):]
148 else:
148 else:
149 # we're outside the repo. return an absolute path.
149 # we're outside the repo. return an absolute path.
150 return cwd
150 return cwd
151
151
152 def pathto(self, f, cwd=None):
152 def pathto(self, f, cwd=None):
153 if cwd is None:
153 if cwd is None:
154 cwd = self.getcwd()
154 cwd = self.getcwd()
155 path = util.pathto(self._root, cwd, f)
155 path = util.pathto(self._root, cwd, f)
156 if self._slash:
156 if self._slash:
157 return util.normpath(path)
157 return util.normpath(path)
158 return path
158 return path
159
159
160 def __getitem__(self, key):
160 def __getitem__(self, key):
161 ''' current states:
161 ''' current states:
162 n normal
162 n normal
163 m needs merging
163 m needs merging
164 r marked for removal
164 r marked for removal
165 a marked for addition
165 a marked for addition
166 ? not tracked'''
166 ? not tracked'''
167 return self._map.get(key, ("?",))[0]
167 return self._map.get(key, ("?",))[0]
168
168
169 def __contains__(self, key):
169 def __contains__(self, key):
170 return key in self._map
170 return key in self._map
171
171
172 def __iter__(self):
172 def __iter__(self):
173 for x in util.sort(self._map):
173 for x in util.sort(self._map):
174 yield x
174 yield x
175
175
176 def parents(self):
176 def parents(self):
177 return self._pl
177 return self._pl
178
178
179 def branch(self):
179 def branch(self):
180 return self._branch
180 return self._branch
181
181
182 def setparents(self, p1, p2=nullid):
182 def setparents(self, p1, p2=nullid):
183 self._dirty = self._dirtypl = True
183 self._dirty = self._dirtypl = True
184 self._pl = p1, p2
184 self._pl = p1, p2
185
185
186 def setbranch(self, branch):
186 def setbranch(self, branch):
187 self._branch = branch
187 self._branch = branch
188 self._opener("branch", "w").write(branch + '\n')
188 self._opener("branch", "w").write(branch + '\n')
189
189
190 def _read(self):
190 def _read(self):
191 self._map = {}
191 self._map = {}
192 self._copymap = {}
192 self._copymap = {}
193 if not self._dirtypl:
194 self._pl = [nullid, nullid]
195 try:
193 try:
196 st = self._opener("dirstate").read()
194 st = self._opener("dirstate").read()
197 except IOError, err:
195 except IOError, err:
198 if err.errno != errno.ENOENT: raise
196 if err.errno != errno.ENOENT: raise
199 return
197 return
200 if not st:
198 if not st:
201 return
199 return
202
200
201 p = parsers.parse_dirstate(self._map, self._copymap, st);
203 if not self._dirtypl:
202 if not self._dirtypl:
204 self._pl = [st[:20], st[20: 40]]
203 self._pl = p
205
206 # deref fields so they will be local in loop
207 dmap = self._map
208 copymap = self._copymap
209 unpack = struct.unpack
210 e_size = struct.calcsize(_format)
211 pos1 = 40
212 l = len(st)
213
214 # the inner loop
215 while pos1 < l:
216 pos2 = pos1 + e_size
217 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
218 pos1 = pos2 + e[4]
219 f = st[pos2:pos1]
220 if '\0' in f:
221 f, c = f.split('\0')
222 copymap[f] = c
223 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
224
204
225 def invalidate(self):
205 def invalidate(self):
226 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
206 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
227 if a in self.__dict__:
207 if a in self.__dict__:
228 delattr(self, a)
208 delattr(self, a)
229 self._dirty = False
209 self._dirty = False
230
210
231 def copy(self, source, dest):
211 def copy(self, source, dest):
232 if source == dest:
212 if source == dest:
233 return
213 return
234 self._dirty = True
214 self._dirty = True
235 self._copymap[dest] = source
215 self._copymap[dest] = source
236
216
237 def copied(self, file):
217 def copied(self, file):
238 return self._copymap.get(file, None)
218 return self._copymap.get(file, None)
239
219
240 def copies(self):
220 def copies(self):
241 return self._copymap
221 return self._copymap
242
222
243 def _droppath(self, f):
223 def _droppath(self, f):
244 if self[f] not in "?r" and "_dirs" in self.__dict__:
224 if self[f] not in "?r" and "_dirs" in self.__dict__:
245 dirs = self._dirs
225 dirs = self._dirs
246 for base in _finddirs(f):
226 for base in _finddirs(f):
247 if dirs[base] == 1:
227 if dirs[base] == 1:
248 del dirs[base]
228 del dirs[base]
249 return
229 return
250 dirs[base] -= 1
230 dirs[base] -= 1
251
231
252 def _addpath(self, f, check=False):
232 def _addpath(self, f, check=False):
253 oldstate = self[f]
233 oldstate = self[f]
254 if check or oldstate == "r":
234 if check or oldstate == "r":
255 if '\r' in f or '\n' in f:
235 if '\r' in f or '\n' in f:
256 raise util.Abort(
236 raise util.Abort(
257 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
237 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
258 if f in self._dirs:
238 if f in self._dirs:
259 raise util.Abort(_('directory %r already in dirstate') % f)
239 raise util.Abort(_('directory %r already in dirstate') % f)
260 # shadows
240 # shadows
261 for d in _finddirs(f):
241 for d in _finddirs(f):
262 if d in self._dirs:
242 if d in self._dirs:
263 break
243 break
264 if d in self._map and self[d] != 'r':
244 if d in self._map and self[d] != 'r':
265 raise util.Abort(
245 raise util.Abort(
266 _('file %r in dirstate clashes with %r') % (d, f))
246 _('file %r in dirstate clashes with %r') % (d, f))
267 if oldstate in "?r" and "_dirs" in self.__dict__:
247 if oldstate in "?r" and "_dirs" in self.__dict__:
268 dirs = self._dirs
248 dirs = self._dirs
269 for base in _finddirs(f):
249 for base in _finddirs(f):
270 dirs[base] = dirs.get(base, 0) + 1
250 dirs[base] = dirs.get(base, 0) + 1
271
251
272 def normal(self, f):
252 def normal(self, f):
273 'mark a file normal and clean'
253 'mark a file normal and clean'
274 self._dirty = True
254 self._dirty = True
275 self._addpath(f)
255 self._addpath(f)
276 s = os.lstat(self._join(f))
256 s = os.lstat(self._join(f))
277 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
257 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime)
278 if f in self._copymap:
258 if f in self._copymap:
279 del self._copymap[f]
259 del self._copymap[f]
280
260
281 def normallookup(self, f):
261 def normallookup(self, f):
282 'mark a file normal, but possibly dirty'
262 'mark a file normal, but possibly dirty'
283 if self._pl[1] != nullid and f in self._map:
263 if self._pl[1] != nullid and f in self._map:
284 # if there is a merge going on and the file was either
264 # if there is a merge going on and the file was either
285 # in state 'm' or dirty before being removed, restore that state.
265 # in state 'm' or dirty before being removed, restore that state.
286 entry = self._map[f]
266 entry = self._map[f]
287 if entry[0] == 'r' and entry[2] in (-1, -2):
267 if entry[0] == 'r' and entry[2] in (-1, -2):
288 source = self._copymap.get(f)
268 source = self._copymap.get(f)
289 if entry[2] == -1:
269 if entry[2] == -1:
290 self.merge(f)
270 self.merge(f)
291 elif entry[2] == -2:
271 elif entry[2] == -2:
292 self.normaldirty(f)
272 self.normaldirty(f)
293 if source:
273 if source:
294 self.copy(source, f)
274 self.copy(source, f)
295 return
275 return
296 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
276 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
297 return
277 return
298 self._dirty = True
278 self._dirty = True
299 self._addpath(f)
279 self._addpath(f)
300 self._map[f] = ('n', 0, -1, -1, 0)
280 self._map[f] = ('n', 0, -1, -1)
301 if f in self._copymap:
281 if f in self._copymap:
302 del self._copymap[f]
282 del self._copymap[f]
303
283
304 def normaldirty(self, f):
284 def normaldirty(self, f):
305 'mark a file normal, but dirty'
285 'mark a file normal, but dirty'
306 self._dirty = True
286 self._dirty = True
307 self._addpath(f)
287 self._addpath(f)
308 self._map[f] = ('n', 0, -2, -1, 0)
288 self._map[f] = ('n', 0, -2, -1)
309 if f in self._copymap:
289 if f in self._copymap:
310 del self._copymap[f]
290 del self._copymap[f]
311
291
312 def add(self, f):
292 def add(self, f):
313 'mark a file added'
293 'mark a file added'
314 self._dirty = True
294 self._dirty = True
315 self._addpath(f, True)
295 self._addpath(f, True)
316 self._map[f] = ('a', 0, -1, -1, 0)
296 self._map[f] = ('a', 0, -1, -1)
317 if f in self._copymap:
297 if f in self._copymap:
318 del self._copymap[f]
298 del self._copymap[f]
319
299
320 def remove(self, f):
300 def remove(self, f):
321 'mark a file removed'
301 'mark a file removed'
322 self._dirty = True
302 self._dirty = True
323 self._droppath(f)
303 self._droppath(f)
324 size = 0
304 size = 0
325 if self._pl[1] != nullid and f in self._map:
305 if self._pl[1] != nullid and f in self._map:
326 entry = self._map[f]
306 entry = self._map[f]
327 if entry[0] == 'm':
307 if entry[0] == 'm':
328 size = -1
308 size = -1
329 elif entry[0] == 'n' and entry[2] == -2:
309 elif entry[0] == 'n' and entry[2] == -2:
330 size = -2
310 size = -2
331 self._map[f] = ('r', 0, size, 0, 0)
311 self._map[f] = ('r', 0, size, 0)
332 if size == 0 and f in self._copymap:
312 if size == 0 and f in self._copymap:
333 del self._copymap[f]
313 del self._copymap[f]
334
314
335 def merge(self, f):
315 def merge(self, f):
336 'mark a file merged'
316 'mark a file merged'
337 self._dirty = True
317 self._dirty = True
338 s = os.lstat(self._join(f))
318 s = os.lstat(self._join(f))
339 self._addpath(f)
319 self._addpath(f)
340 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
320 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime)
341 if f in self._copymap:
321 if f in self._copymap:
342 del self._copymap[f]
322 del self._copymap[f]
343
323
344 def forget(self, f):
324 def forget(self, f):
345 'forget a file'
325 'forget a file'
346 self._dirty = True
326 self._dirty = True
347 try:
327 try:
348 self._droppath(f)
328 self._droppath(f)
349 del self._map[f]
329 del self._map[f]
350 except KeyError:
330 except KeyError:
351 self._ui.warn(_("not in dirstate: %s\n") % f)
331 self._ui.warn(_("not in dirstate: %s\n") % f)
352
332
353 def _normalize(self, path, knownpath=False):
333 def _normalize(self, path, knownpath=False):
354 norm_path = os.path.normcase(path)
334 norm_path = os.path.normcase(path)
355 fold_path = self._foldmap.get(norm_path, None)
335 fold_path = self._foldmap.get(norm_path, None)
356 if fold_path is None:
336 if fold_path is None:
357 if knownpath or not os.path.exists(os.path.join(self._root, path)):
337 if knownpath or not os.path.exists(os.path.join(self._root, path)):
358 fold_path = path
338 fold_path = path
359 else:
339 else:
360 fold_path = self._foldmap.setdefault(norm_path,
340 fold_path = self._foldmap.setdefault(norm_path,
361 util.fspath(path, self._root))
341 util.fspath(path, self._root))
362 return fold_path
342 return fold_path
363
343
364 def clear(self):
344 def clear(self):
365 self._map = {}
345 self._map = {}
366 if "_dirs" in self.__dict__:
346 if "_dirs" in self.__dict__:
367 delattr(self, "_dirs");
347 delattr(self, "_dirs");
368 self._copymap = {}
348 self._copymap = {}
369 self._pl = [nullid, nullid]
349 self._pl = [nullid, nullid]
370 self._dirty = True
350 self._dirty = True
371
351
372 def rebuild(self, parent, files):
352 def rebuild(self, parent, files):
373 self.clear()
353 self.clear()
374 for f in files:
354 for f in files:
375 if 'x' in files.flags(f):
355 if 'x' in files.flags(f):
376 self._map[f] = ('n', 0777, -1, 0, 0)
356 self._map[f] = ('n', 0777, -1, 0)
377 else:
357 else:
378 self._map[f] = ('n', 0666, -1, 0, 0)
358 self._map[f] = ('n', 0666, -1, 0)
379 self._pl = (parent, nullid)
359 self._pl = (parent, nullid)
380 self._dirty = True
360 self._dirty = True
381
361
382 def write(self):
362 def write(self):
383 if not self._dirty:
363 if not self._dirty:
384 return
364 return
385 st = self._opener("dirstate", "w", atomictemp=True)
365 st = self._opener("dirstate", "w", atomictemp=True)
386
366
387 try:
367 try:
388 gran = int(self._ui.config('dirstate', 'granularity', 1))
368 gran = int(self._ui.config('dirstate', 'granularity', 1))
389 except ValueError:
369 except ValueError:
390 gran = 1
370 gran = 1
391 limit = sys.maxint
371 limit = sys.maxint
392 if gran > 0:
372 if gran > 0:
393 limit = util.fstat(st).st_mtime - gran
373 limit = util.fstat(st).st_mtime - gran
394
374
395 cs = cStringIO.StringIO()
375 cs = cStringIO.StringIO()
396 copymap = self._copymap
376 copymap = self._copymap
397 pack = struct.pack
377 pack = struct.pack
398 write = cs.write
378 write = cs.write
399 write("".join(self._pl))
379 write("".join(self._pl))
400 for f, e in self._map.iteritems():
380 for f, e in self._map.iteritems():
401 if f in copymap:
381 if f in copymap:
402 f = "%s\0%s" % (f, copymap[f])
382 f = "%s\0%s" % (f, copymap[f])
403 if e[3] > limit and e[0] == 'n':
383 if e[3] > limit and e[0] == 'n':
404 e = (e[0], 0, -1, -1, 0)
384 e = (e[0], 0, -1, -1)
405 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
385 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
406 write(e)
386 write(e)
407 write(f)
387 write(f)
408 st.write(cs.getvalue())
388 st.write(cs.getvalue())
409 st.rename()
389 st.rename()
410 self._dirty = self._dirtypl = False
390 self._dirty = self._dirtypl = False
411
391
412 def _dirignore(self, f):
392 def _dirignore(self, f):
413 if f == '.':
393 if f == '.':
414 return False
394 return False
415 if self._ignore(f):
395 if self._ignore(f):
416 return True
396 return True
417 for p in _finddirs(f):
397 for p in _finddirs(f):
418 if self._ignore(p):
398 if self._ignore(p):
419 return True
399 return True
420 return False
400 return False
421
401
422 def walk(self, match, unknown, ignored):
402 def walk(self, match, unknown, ignored):
423 '''
403 '''
424 walk recursively through the directory tree, finding all files
404 walk recursively through the directory tree, finding all files
425 matched by the match function
405 matched by the match function
426
406
427 results are yielded in a tuple (filename, stat), where stat
407 results are yielded in a tuple (filename, stat), where stat
428 and st is the stat result if the file was found in the directory.
408 and st is the stat result if the file was found in the directory.
429 '''
409 '''
430
410
431 def fwarn(f, msg):
411 def fwarn(f, msg):
432 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
412 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
433 return False
413 return False
434 badfn = fwarn
414 badfn = fwarn
435 if hasattr(match, 'bad'):
415 if hasattr(match, 'bad'):
436 badfn = match.bad
416 badfn = match.bad
437
417
438 def badtype(f, mode):
418 def badtype(f, mode):
439 kind = 'unknown'
419 kind = 'unknown'
440 if stat.S_ISCHR(mode): kind = _('character device')
420 if stat.S_ISCHR(mode): kind = _('character device')
441 elif stat.S_ISBLK(mode): kind = _('block device')
421 elif stat.S_ISBLK(mode): kind = _('block device')
442 elif stat.S_ISFIFO(mode): kind = _('fifo')
422 elif stat.S_ISFIFO(mode): kind = _('fifo')
443 elif stat.S_ISSOCK(mode): kind = _('socket')
423 elif stat.S_ISSOCK(mode): kind = _('socket')
444 elif stat.S_ISDIR(mode): kind = _('directory')
424 elif stat.S_ISDIR(mode): kind = _('directory')
445 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
425 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
446 % (self.pathto(f), kind))
426 % (self.pathto(f), kind))
447
427
448 ignore = self._ignore
428 ignore = self._ignore
449 dirignore = self._dirignore
429 dirignore = self._dirignore
450 if ignored:
430 if ignored:
451 ignore = util.never
431 ignore = util.never
452 dirignore = util.never
432 dirignore = util.never
453 elif not unknown:
433 elif not unknown:
454 # if unknown and ignored are False, skip step 2
434 # if unknown and ignored are False, skip step 2
455 ignore = util.always
435 ignore = util.always
456 dirignore = util.always
436 dirignore = util.always
457
437
458 matchfn = match.matchfn
438 matchfn = match.matchfn
459 dmap = self._map
439 dmap = self._map
460 normpath = util.normpath
440 normpath = util.normpath
461 normalize = self.normalize
441 normalize = self.normalize
462 listdir = osutil.listdir
442 listdir = osutil.listdir
463 lstat = os.lstat
443 lstat = os.lstat
464 pconvert = util.pconvert
444 pconvert = util.pconvert
465 getkind = stat.S_IFMT
445 getkind = stat.S_IFMT
466 dirkind = stat.S_IFDIR
446 dirkind = stat.S_IFDIR
467 regkind = stat.S_IFREG
447 regkind = stat.S_IFREG
468 lnkkind = stat.S_IFLNK
448 lnkkind = stat.S_IFLNK
469 join = self._join
449 join = self._join
470 work = []
450 work = []
471 wadd = work.append
451 wadd = work.append
472
452
473 files = util.unique(match.files())
453 files = util.unique(match.files())
474 if not files or '.' in files:
454 if not files or '.' in files:
475 files = ['']
455 files = ['']
476 results = {'.hg': None}
456 results = {'.hg': None}
477
457
478 # step 1: find all explicit files
458 # step 1: find all explicit files
479 for ff in util.sort(files):
459 for ff in util.sort(files):
480 nf = normalize(normpath(ff))
460 nf = normalize(normpath(ff))
481 if nf in results:
461 if nf in results:
482 continue
462 continue
483
463
484 try:
464 try:
485 st = lstat(join(nf))
465 st = lstat(join(nf))
486 kind = getkind(st.st_mode)
466 kind = getkind(st.st_mode)
487 if kind == dirkind:
467 if kind == dirkind:
488 if not dirignore(nf):
468 if not dirignore(nf):
489 wadd(nf)
469 wadd(nf)
490 elif kind == regkind or kind == lnkkind:
470 elif kind == regkind or kind == lnkkind:
491 results[nf] = st
471 results[nf] = st
492 else:
472 else:
493 badtype(ff, kind)
473 badtype(ff, kind)
494 if nf in dmap:
474 if nf in dmap:
495 results[nf] = None
475 results[nf] = None
496 except OSError, inst:
476 except OSError, inst:
497 keep = False
477 keep = False
498 prefix = nf + "/"
478 prefix = nf + "/"
499 for fn in dmap:
479 for fn in dmap:
500 if nf == fn or fn.startswith(prefix):
480 if nf == fn or fn.startswith(prefix):
501 keep = True
481 keep = True
502 break
482 break
503 if not keep:
483 if not keep:
504 if inst.errno != errno.ENOENT:
484 if inst.errno != errno.ENOENT:
505 fwarn(ff, inst.strerror)
485 fwarn(ff, inst.strerror)
506 elif badfn(ff, inst.strerror):
486 elif badfn(ff, inst.strerror):
507 if (nf in dmap or not ignore(nf)) and matchfn(nf):
487 if (nf in dmap or not ignore(nf)) and matchfn(nf):
508 results[nf] = None
488 results[nf] = None
509
489
510 # step 2: visit subdirectories
490 # step 2: visit subdirectories
511 while work:
491 while work:
512 nd = work.pop()
492 nd = work.pop()
513 if hasattr(match, 'dir'):
493 if hasattr(match, 'dir'):
514 match.dir(nd)
494 match.dir(nd)
515 if nd == '.':
495 if nd == '.':
516 nd = ''
496 nd = ''
517 entries = listdir(join(nd), stat=True)
497 entries = listdir(join(nd), stat=True)
518 else:
498 else:
519 entries = listdir(join(nd), stat=True, skip ='.hg')
499 entries = listdir(join(nd), stat=True, skip ='.hg')
520 for f, kind, st in entries:
500 for f, kind, st in entries:
521 nf = normalize(nd and (nd + "/" + f) or f, True)
501 nf = normalize(nd and (nd + "/" + f) or f, True)
522 if nf not in results:
502 if nf not in results:
523 if kind == dirkind:
503 if kind == dirkind:
524 if not ignore(nf):
504 if not ignore(nf):
525 wadd(nf)
505 wadd(nf)
526 if nf in dmap and matchfn(nf):
506 if nf in dmap and matchfn(nf):
527 results[nf] = None
507 results[nf] = None
528 elif kind == regkind or kind == lnkkind:
508 elif kind == regkind or kind == lnkkind:
529 if nf in dmap:
509 if nf in dmap:
530 if matchfn(nf):
510 if matchfn(nf):
531 results[nf] = st
511 results[nf] = st
532 elif matchfn(nf) and not ignore(nf):
512 elif matchfn(nf) and not ignore(nf):
533 results[nf] = st
513 results[nf] = st
534 elif nf in dmap and matchfn(nf):
514 elif nf in dmap and matchfn(nf):
535 results[nf] = None
515 results[nf] = None
536
516
537 # step 3: report unseen items in the dmap hash
517 # step 3: report unseen items in the dmap hash
538 visit = [f for f in dmap if f not in results and match(f)]
518 visit = [f for f in dmap if f not in results and match(f)]
539 for nf in util.sort(visit):
519 for nf in util.sort(visit):
540 results[nf] = None
520 results[nf] = None
541 try:
521 try:
542 st = lstat(join(nf))
522 st = lstat(join(nf))
543 kind = getkind(st.st_mode)
523 kind = getkind(st.st_mode)
544 if kind == regkind or kind == lnkkind:
524 if kind == regkind or kind == lnkkind:
545 results[nf] = st
525 results[nf] = st
546 except OSError, inst:
526 except OSError, inst:
547 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
527 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
548 raise
528 raise
549
529
550 del results['.hg']
530 del results['.hg']
551 return results
531 return results
552
532
553 def status(self, match, ignored, clean, unknown):
533 def status(self, match, ignored, clean, unknown):
554 listignored, listclean, listunknown = ignored, clean, unknown
534 listignored, listclean, listunknown = ignored, clean, unknown
555 lookup, modified, added, unknown, ignored = [], [], [], [], []
535 lookup, modified, added, unknown, ignored = [], [], [], [], []
556 removed, deleted, clean = [], [], []
536 removed, deleted, clean = [], [], []
557
537
558 _join = self._join
538 _join = self._join
559 lstat = os.lstat
539 lstat = os.lstat
560 cmap = self._copymap
540 cmap = self._copymap
561 dmap = self._map
541 dmap = self._map
562 ladd = lookup.append
542 ladd = lookup.append
563 madd = modified.append
543 madd = modified.append
564 aadd = added.append
544 aadd = added.append
565 uadd = unknown.append
545 uadd = unknown.append
566 iadd = ignored.append
546 iadd = ignored.append
567 radd = removed.append
547 radd = removed.append
568 dadd = deleted.append
548 dadd = deleted.append
569 cadd = clean.append
549 cadd = clean.append
570
550
571 for fn, st in self.walk(match, listunknown, listignored).iteritems():
551 for fn, st in self.walk(match, listunknown, listignored).iteritems():
572 if fn not in dmap:
552 if fn not in dmap:
573 if (listignored or match.exact(fn)) and self._dirignore(fn):
553 if (listignored or match.exact(fn)) and self._dirignore(fn):
574 if listignored:
554 if listignored:
575 iadd(fn)
555 iadd(fn)
576 elif listunknown:
556 elif listunknown:
577 uadd(fn)
557 uadd(fn)
578 continue
558 continue
579
559
580 state, mode, size, time, foo = dmap[fn]
560 state, mode, size, time = dmap[fn]
581
561
582 if not st and state in "nma":
562 if not st and state in "nma":
583 dadd(fn)
563 dadd(fn)
584 elif state == 'n':
564 elif state == 'n':
585 if (size >= 0 and
565 if (size >= 0 and
586 (size != st.st_size
566 (size != st.st_size
587 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
567 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
588 or size == -2
568 or size == -2
589 or fn in self._copymap):
569 or fn in self._copymap):
590 madd(fn)
570 madd(fn)
591 elif time != int(st.st_mtime):
571 elif time != int(st.st_mtime):
592 ladd(fn)
572 ladd(fn)
593 elif listclean:
573 elif listclean:
594 cadd(fn)
574 cadd(fn)
595 elif state == 'm':
575 elif state == 'm':
596 madd(fn)
576 madd(fn)
597 elif state == 'a':
577 elif state == 'a':
598 aadd(fn)
578 aadd(fn)
599 elif state == 'r':
579 elif state == 'r':
600 radd(fn)
580 radd(fn)
601
581
602 return (lookup, modified, added, removed, deleted, unknown, ignored,
582 return (lookup, modified, added, removed, deleted, unknown, ignored,
603 clean)
583 clean)
@@ -1,146 +1,248 b''
1 /*
1 /*
2 parsers.c - efficient content parsing
2 parsers.c - efficient content parsing
3
3
4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
5
5
6 This software may be used and distributed according to the terms of
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
7 the GNU General Public License, incorporated herein by reference.
8 */
8 */
9
9
10 #include <Python.h>
10 #include <Python.h>
11 #include <ctype.h>
11 #include <ctype.h>
12 #include <string.h>
12 #include <string.h>
13
13
14 static int hexdigit(char c)
14 static int hexdigit(char c)
15 {
15 {
16 if (c >= '0' && c <= '9')
16 if (c >= '0' && c <= '9')
17 return c - '0';
17 return c - '0';
18 if (c >= 'a' && c <= 'f')
18 if (c >= 'a' && c <= 'f')
19 return c - 'a' + 10;
19 return c - 'a' + 10;
20 if (c >= 'A' && c <= 'F')
20 if (c >= 'A' && c <= 'F')
21 return c - 'A' + 10;
21 return c - 'A' + 10;
22
22
23 PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
23 PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
24 return 0;
24 return 0;
25 }
25 }
26
26
27 /*
27 /*
28 * Turn a hex-encoded string into binary.
28 * Turn a hex-encoded string into binary.
29 */
29 */
30 static PyObject *unhexlify(const char *str, int len)
30 static PyObject *unhexlify(const char *str, int len)
31 {
31 {
32 PyObject *ret;
32 PyObject *ret;
33 const char *c;
33 const char *c;
34 char *d;
34 char *d;
35
35
36 ret = PyString_FromStringAndSize(NULL, len / 2);
36 ret = PyString_FromStringAndSize(NULL, len / 2);
37 if (!ret)
37 if (!ret)
38 return NULL;
38 return NULL;
39
39
40 d = PyString_AS_STRING(ret);
40 d = PyString_AS_STRING(ret);
41 for (c = str; c < str + len;) {
41 for (c = str; c < str + len;) {
42 int hi = hexdigit(*c++);
42 int hi = hexdigit(*c++);
43 int lo = hexdigit(*c++);
43 int lo = hexdigit(*c++);
44 *d++ = (hi << 4) | lo;
44 *d++ = (hi << 4) | lo;
45 }
45 }
46
46
47 return ret;
47 return ret;
48 }
48 }
49
49
50 /*
50 /*
51 * This code assumes that a manifest is stitched together with newline
51 * This code assumes that a manifest is stitched together with newline
52 * ('\n') characters.
52 * ('\n') characters.
53 */
53 */
54 static PyObject *parse_manifest(PyObject *self, PyObject *args)
54 static PyObject *parse_manifest(PyObject *self, PyObject *args)
55 {
55 {
56 PyObject *mfdict, *fdict;
56 PyObject *mfdict, *fdict;
57 char *str, *cur, *start, *zero;
57 char *str, *cur, *start, *zero;
58 int len;
58 int len;
59
59
60 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
60 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
61 &PyDict_Type, &mfdict,
61 &PyDict_Type, &mfdict,
62 &PyDict_Type, &fdict,
62 &PyDict_Type, &fdict,
63 &str, &len))
63 &str, &len))
64 goto quit;
64 goto quit;
65
65
66 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
66 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
67 PyObject *file = NULL, *node = NULL;
67 PyObject *file = NULL, *node = NULL;
68 PyObject *flags = NULL;
68 PyObject *flags = NULL;
69 int nlen;
69 int nlen;
70
70
71 if (!*cur) {
71 if (!*cur) {
72 zero = cur;
72 zero = cur;
73 continue;
73 continue;
74 }
74 }
75 else if (*cur != '\n')
75 else if (*cur != '\n')
76 continue;
76 continue;
77
77
78 if (!zero) {
78 if (!zero) {
79 PyErr_SetString(PyExc_ValueError,
79 PyErr_SetString(PyExc_ValueError,
80 "manifest entry has no separator");
80 "manifest entry has no separator");
81 goto quit;
81 goto quit;
82 }
82 }
83
83
84 file = PyString_FromStringAndSize(start, zero - start);
84 file = PyString_FromStringAndSize(start, zero - start);
85 if (!file)
85 if (!file)
86 goto bail;
86 goto bail;
87
87
88 nlen = cur - zero - 1;
88 nlen = cur - zero - 1;
89
89
90 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
90 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
91 if (!node)
91 if (!node)
92 goto bail;
92 goto bail;
93
93
94 if (nlen > 40) {
94 if (nlen > 40) {
95 PyObject *flags;
95 PyObject *flags;
96
96
97 flags = PyString_FromStringAndSize(zero + 41,
97 flags = PyString_FromStringAndSize(zero + 41,
98 nlen - 40);
98 nlen - 40);
99 if (!flags)
99 if (!flags)
100 goto bail;
100 goto bail;
101
101
102 if (PyDict_SetItem(fdict, file, flags) == -1)
102 if (PyDict_SetItem(fdict, file, flags) == -1)
103 goto bail;
103 goto bail;
104 }
104 }
105
105
106 if (PyDict_SetItem(mfdict, file, node) == -1)
106 if (PyDict_SetItem(mfdict, file, node) == -1)
107 goto bail;
107 goto bail;
108
108
109 start = cur + 1;
109 start = cur + 1;
110 zero = NULL;
110 zero = NULL;
111
111
112 Py_XDECREF(flags);
112 Py_XDECREF(flags);
113 Py_XDECREF(node);
113 Py_XDECREF(node);
114 Py_XDECREF(file);
114 Py_XDECREF(file);
115 continue;
115 continue;
116 bail:
116 bail:
117 Py_XDECREF(flags);
117 Py_XDECREF(flags);
118 Py_XDECREF(node);
118 Py_XDECREF(node);
119 Py_XDECREF(file);
119 Py_XDECREF(file);
120 goto quit;
120 goto quit;
121 }
121 }
122
122
123 if (len > 0 && *(cur - 1) != '\n') {
123 if (len > 0 && *(cur - 1) != '\n') {
124 PyErr_SetString(PyExc_ValueError,
124 PyErr_SetString(PyExc_ValueError,
125 "manifest contains trailing garbage");
125 "manifest contains trailing garbage");
126 goto quit;
126 goto quit;
127 }
127 }
128
128
129 Py_INCREF(Py_None);
129 Py_INCREF(Py_None);
130 return Py_None;
130 return Py_None;
131
132 quit:
131 quit:
133 return NULL;
132 return NULL;
134 }
133 }
135
134
135 #ifdef _WIN32
136 # ifdef _MSC_VER
137 /* msvc 6.0 has problems */
138 # define inline __inline
139 typedef unsigned long uint32_t;
140 # else
141 # include <stdint.h>
142 # endif
143 static uint32_t ntohl(uint32_t x)
144 {
145 return ((x & 0x000000ffUL) << 24) |
146 ((x & 0x0000ff00UL) << 8) |
147 ((x & 0x00ff0000UL) >> 8) |
148 ((x & 0xff000000UL) >> 24);
149 }
150 #else
151 /* not windows */
152 # include <sys/types.h>
153 # if defined __BEOS__ && !defined __HAIKU__
154 # include <ByteOrder.h>
155 # else
156 # include <arpa/inet.h>
157 # endif
158 # include <inttypes.h>
159 #endif
160
161 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
162 {
163 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
164 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
165 char *str, *cur, *end, *cpos;
166 int state, mode, size, mtime, flen;
167 int len;
168 char decode[16]; /* for alignment */
169
170 if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
171 &PyDict_Type, &dmap,
172 &PyDict_Type, &cmap,
173 &str, &len))
174 goto quit;
175
176 /* read parents */
177 if (len < 40)
178 goto quit;
179
180 parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
181 if (!parents)
182 goto quit;
183
184 /* read filenames */
185 cur = str + 40;
186 end = str + len;
187
188 while (cur < end - 17) {
189 /* unpack header */
190 state = *cur;
191 memcpy(decode, cur + 1, 16);
192 mode = ntohl(*(uint32_t *)(decode));
193 size = ntohl(*(uint32_t *)(decode + 4));
194 mtime = ntohl(*(uint32_t *)(decode + 8));
195 flen = ntohl(*(uint32_t *)(decode + 12));
196 cur += 17;
197 if (cur + flen > end)
198 goto quit;
199
200 entry = Py_BuildValue("ciii", state, mode, size, mtime);
201 PyObject_GC_UnTrack(entry); /* don't waste time with this */
202 if (!entry)
203 goto quit;
204
205 cpos = memchr(cur, 0, flen);
206 if (cpos) {
207 fname = PyString_FromStringAndSize(cur, cpos - cur);
208 cname = PyString_FromStringAndSize(cpos + 1,
209 flen - (cpos - cur) - 1);
210 if (!fname || !cname ||
211 PyDict_SetItem(cmap, fname, cname) == -1 ||
212 PyDict_SetItem(dmap, fname, entry) == -1)
213 goto quit;
214 Py_DECREF(cname);
215 } else {
216 fname = PyString_FromStringAndSize(cur, flen);
217 if (!fname ||
218 PyDict_SetItem(dmap, fname, entry) == -1)
219 goto quit;
220 }
221 cur += flen;
222 Py_DECREF(fname);
223 Py_DECREF(entry);
224 fname = cname = entry = NULL;
225 }
226
227 ret = parents;
228 Py_INCREF(ret);
229 quit:
230 Py_XDECREF(fname);
231 Py_XDECREF(cname);
232 Py_XDECREF(entry);
233 Py_XDECREF(parents);
234 return ret;
235 }
236
136 static char parsers_doc[] = "Efficient content parsing.";
237 static char parsers_doc[] = "Efficient content parsing.";
137
238
138 static PyMethodDef methods[] = {
239 static PyMethodDef methods[] = {
139 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
240 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
241 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
140 {NULL, NULL}
242 {NULL, NULL}
141 };
243 };
142
244
143 PyMODINIT_FUNC initparsers(void)
245 PyMODINIT_FUNC initparsers(void)
144 {
246 {
145 Py_InitModule3("parsers", methods, parsers_doc);
247 Py_InitModule3("parsers", methods, parsers_doc);
146 }
248 }
General Comments 0
You need to be logged in to leave comments. Login now