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