##// END OF EJS Templates
issue352: disallow '\n' and '\r' in filenames (dirstate and manifest)
Benoit Boissinot -
r3607:f4c9bb4a default
parent child Browse files
Show More
@@ -0,0 +1,21
1 #!/bin/bash
2 # http://www.selenic.com/mercurial/bts/issue352
3
4 hg init foo
5 cd foo
6
7 A=`echo -e -n 'he\rllo'`
8
9 echo foo > "hell
10 o"
11 echo foo > "$A"
12 hg add
13 hg ci -A -m m
14 rm "$A"
15 ls
16 hg add
17 # BUG ? we don't walk on filenames with '\n' (regexp related) ?
18 hg debugwalk
19 hg ci -A -m m
20
21 exit 0
@@ -0,0 +1,7
1 adding he llo
2 abort: '\n' and '\r' disallowed in filenames
3 adding he llo
4 abort: '\n' and '\r' disallowed in filenames
5 hell
6 o
7 nothing changed
@@ -1,525 +1,529
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005, 2006 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 *
11 11 from i18n import gettext as _
12 12 from demandload import *
13 13 demandload(globals(), "struct os time bisect stat strutil util re errno")
14 14
15 15 class dirstate(object):
16 16 format = ">cllll"
17 17
18 18 def __init__(self, opener, ui, root):
19 19 self.opener = opener
20 20 self.root = root
21 21 self.dirty = 0
22 22 self.ui = ui
23 23 self.map = None
24 24 self.pl = None
25 25 self.dirs = None
26 26 self.copymap = {}
27 27 self.ignorefunc = None
28 28
29 29 def wjoin(self, f):
30 30 return os.path.join(self.root, f)
31 31
32 32 def getcwd(self):
33 33 cwd = os.getcwd()
34 34 if cwd == self.root: return ''
35 35 return cwd[len(self.root) + 1:]
36 36
37 37 def hgignore(self):
38 38 '''return the contents of .hgignore files as a list of patterns.
39 39
40 40 the files parsed for patterns include:
41 41 .hgignore in the repository root
42 42 any additional files specified in the [ui] section of ~/.hgrc
43 43
44 44 trailing white space is dropped.
45 45 the escape character is backslash.
46 46 comments start with #.
47 47 empty lines are skipped.
48 48
49 49 lines can be of the following formats:
50 50
51 51 syntax: regexp # defaults following lines to non-rooted regexps
52 52 syntax: glob # defaults following lines to non-rooted globs
53 53 re:pattern # non-rooted regular expression
54 54 glob:pattern # non-rooted glob
55 55 pattern # pattern of the current default type'''
56 56 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
57 57 def parselines(fp):
58 58 for line in fp:
59 59 escape = False
60 60 for i in xrange(len(line)):
61 61 if escape: escape = False
62 62 elif line[i] == '\\': escape = True
63 63 elif line[i] == '#': break
64 64 line = line[:i].rstrip()
65 65 if line: yield line
66 66 repoignore = self.wjoin('.hgignore')
67 67 files = [repoignore]
68 68 files.extend(self.ui.hgignorefiles())
69 69 pats = {}
70 70 for f in files:
71 71 try:
72 72 pats[f] = []
73 73 fp = open(f)
74 74 syntax = 'relre:'
75 75 for line in parselines(fp):
76 76 if line.startswith('syntax:'):
77 77 s = line[7:].strip()
78 78 try:
79 79 syntax = syntaxes[s]
80 80 except KeyError:
81 81 self.ui.warn(_("%s: ignoring invalid "
82 82 "syntax '%s'\n") % (f, s))
83 83 continue
84 84 pat = syntax + line
85 85 for s in syntaxes.values():
86 86 if line.startswith(s):
87 87 pat = line
88 88 break
89 89 pats[f].append(pat)
90 90 except IOError, inst:
91 91 if f != repoignore:
92 92 self.ui.warn(_("skipping unreadable ignore file"
93 93 " '%s': %s\n") % (f, inst.strerror))
94 94 return pats
95 95
96 96 def ignore(self, fn):
97 97 '''default match function used by dirstate and
98 98 localrepository. this honours the repository .hgignore file
99 99 and any other files specified in the [ui] section of .hgrc.'''
100 100 if not self.ignorefunc:
101 101 ignore = self.hgignore()
102 102 allpats = []
103 103 [allpats.extend(patlist) for patlist in ignore.values()]
104 104 if allpats:
105 105 try:
106 106 files, self.ignorefunc, anypats = (
107 107 util.matcher(self.root, inc=allpats, src='.hgignore'))
108 108 except util.Abort:
109 109 # Re-raise an exception where the src is the right file
110 110 for f, patlist in ignore.items():
111 111 files, self.ignorefunc, anypats = (
112 112 util.matcher(self.root, inc=patlist, src=f))
113 113 else:
114 114 self.ignorefunc = util.never
115 115 return self.ignorefunc(fn)
116 116
117 117 def __del__(self):
118 118 if self.dirty:
119 119 self.write()
120 120
121 121 def __getitem__(self, key):
122 122 try:
123 123 return self.map[key]
124 124 except TypeError:
125 125 self.lazyread()
126 126 return self[key]
127 127
128 128 def __contains__(self, key):
129 129 self.lazyread()
130 130 return key in self.map
131 131
132 132 def parents(self):
133 133 self.lazyread()
134 134 return self.pl
135 135
136 136 def markdirty(self):
137 137 if not self.dirty:
138 138 self.dirty = 1
139 139
140 140 def setparents(self, p1, p2=nullid):
141 141 self.lazyread()
142 142 self.markdirty()
143 143 self.pl = p1, p2
144 144
145 145 def state(self, key):
146 146 try:
147 147 return self[key][0]
148 148 except KeyError:
149 149 return "?"
150 150
151 151 def lazyread(self):
152 152 if self.map is None:
153 153 self.read()
154 154
155 155 def parse(self, st):
156 156 self.pl = [st[:20], st[20: 40]]
157 157
158 158 # deref fields so they will be local in loop
159 159 map = self.map
160 160 copymap = self.copymap
161 161 format = self.format
162 162 unpack = struct.unpack
163 163
164 164 pos = 40
165 165 e_size = struct.calcsize(format)
166 166
167 167 while pos < len(st):
168 168 newpos = pos + e_size
169 169 e = unpack(format, st[pos:newpos])
170 170 l = e[4]
171 171 pos = newpos
172 172 newpos = pos + l
173 173 f = st[pos:newpos]
174 174 if '\0' in f:
175 175 f, c = f.split('\0')
176 176 copymap[f] = c
177 177 map[f] = e[:4]
178 178 pos = newpos
179 179
180 180 def read(self):
181 181 self.map = {}
182 182 self.pl = [nullid, nullid]
183 183 try:
184 184 st = self.opener("dirstate").read()
185 185 if st:
186 186 self.parse(st)
187 187 except IOError, err:
188 188 if err.errno != errno.ENOENT: raise
189 189
190 190 def copy(self, source, dest):
191 191 self.lazyread()
192 192 self.markdirty()
193 193 self.copymap[dest] = source
194 194
195 195 def copied(self, file):
196 196 return self.copymap.get(file, None)
197 197
198 198 def copies(self):
199 199 return self.copymap
200 200
201 201 def initdirs(self):
202 202 if self.dirs is None:
203 203 self.dirs = {}
204 204 for f in self.map:
205 205 self.updatedirs(f, 1)
206 206
207 207 def updatedirs(self, path, delta):
208 208 if self.dirs is not None:
209 209 for c in strutil.findall(path, '/'):
210 210 pc = path[:c]
211 211 self.dirs.setdefault(pc, 0)
212 212 self.dirs[pc] += delta
213 213
214 def checkshadows(self, files):
214 def checkinterfering(self, files):
215 215 def prefixes(f):
216 216 for c in strutil.rfindall(f, '/'):
217 217 yield f[:c]
218 218 self.lazyread()
219 219 self.initdirs()
220 220 seendirs = {}
221 221 for f in files:
222 # shadows
222 223 if self.dirs.get(f):
223 224 raise util.Abort(_('directory named %r already in dirstate') %
224 225 f)
225 226 for d in prefixes(f):
226 227 if d in seendirs:
227 228 break
228 229 if d in self.map:
229 230 raise util.Abort(_('file named %r already in dirstate') %
230 231 d)
231 232 seendirs[d] = True
233 # disallowed
234 if '\r' in f or '\n' in f:
235 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
232 236
233 237 def update(self, files, state, **kw):
234 238 ''' current states:
235 239 n normal
236 240 m needs merging
237 241 r marked for removal
238 242 a marked for addition'''
239 243
240 244 if not files: return
241 245 self.lazyread()
242 246 self.markdirty()
243 247 if state == "a":
244 248 self.initdirs()
245 self.checkshadows(files)
249 self.checkinterfering(files)
246 250 for f in files:
247 251 if state == "r":
248 252 self.map[f] = ('r', 0, 0, 0)
249 253 self.updatedirs(f, -1)
250 254 else:
251 255 if state == "a":
252 256 self.updatedirs(f, 1)
253 257 s = os.lstat(self.wjoin(f))
254 258 st_size = kw.get('st_size', s.st_size)
255 259 st_mtime = kw.get('st_mtime', s.st_mtime)
256 260 self.map[f] = (state, s.st_mode, st_size, st_mtime)
257 261 if self.copymap.has_key(f):
258 262 del self.copymap[f]
259 263
260 264 def forget(self, files):
261 265 if not files: return
262 266 self.lazyread()
263 267 self.markdirty()
264 268 self.initdirs()
265 269 for f in files:
266 270 try:
267 271 del self.map[f]
268 272 self.updatedirs(f, -1)
269 273 except KeyError:
270 274 self.ui.warn(_("not in dirstate: %s!\n") % f)
271 275 pass
272 276
273 277 def clear(self):
274 278 self.map = {}
275 279 self.copymap = {}
276 280 self.dirs = None
277 281 self.markdirty()
278 282
279 283 def rebuild(self, parent, files):
280 284 self.clear()
281 285 umask = os.umask(0)
282 286 os.umask(umask)
283 287 for f in files:
284 288 if files.execf(f):
285 289 self.map[f] = ('n', ~umask, -1, 0)
286 290 else:
287 291 self.map[f] = ('n', ~umask & 0666, -1, 0)
288 292 self.pl = (parent, nullid)
289 293 self.markdirty()
290 294
291 295 def write(self):
292 296 if not self.dirty:
293 297 return
294 298 st = self.opener("dirstate", "w", atomic=True)
295 299 st.write("".join(self.pl))
296 300 for f, e in self.map.items():
297 301 c = self.copied(f)
298 302 if c:
299 303 f = f + "\0" + c
300 304 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
301 305 st.write(e + f)
302 306 self.dirty = 0
303 307
304 308 def filterfiles(self, files):
305 309 ret = {}
306 310 unknown = []
307 311
308 312 for x in files:
309 313 if x == '.':
310 314 return self.map.copy()
311 315 if x not in self.map:
312 316 unknown.append(x)
313 317 else:
314 318 ret[x] = self.map[x]
315 319
316 320 if not unknown:
317 321 return ret
318 322
319 323 b = self.map.keys()
320 324 b.sort()
321 325 blen = len(b)
322 326
323 327 for x in unknown:
324 328 bs = bisect.bisect(b, "%s%s" % (x, '/'))
325 329 while bs < blen:
326 330 s = b[bs]
327 331 if len(s) > len(x) and s.startswith(x):
328 332 ret[s] = self.map[s]
329 333 else:
330 334 break
331 335 bs += 1
332 336 return ret
333 337
334 338 def supported_type(self, f, st, verbose=False):
335 339 if stat.S_ISREG(st.st_mode):
336 340 return True
337 341 if verbose:
338 342 kind = 'unknown'
339 343 if stat.S_ISCHR(st.st_mode): kind = _('character device')
340 344 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
341 345 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
342 346 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
343 347 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
344 348 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
345 349 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
346 350 util.pathto(self.getcwd(), f),
347 351 kind))
348 352 return False
349 353
350 354 def walk(self, files=None, match=util.always, badmatch=None):
351 355 # filter out the stat
352 356 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
353 357 yield src, f
354 358
355 359 def statwalk(self, files=None, match=util.always, ignored=False,
356 360 badmatch=None):
357 361 '''
358 362 walk recursively through the directory tree, finding all files
359 363 matched by the match function
360 364
361 365 results are yielded in a tuple (src, filename, st), where src
362 366 is one of:
363 367 'f' the file was found in the directory tree
364 368 'm' the file was only in the dirstate and not in the tree
365 369 'b' file was not found and matched badmatch
366 370
367 371 and st is the stat result if the file was found in the directory.
368 372 '''
369 373 self.lazyread()
370 374
371 375 # walk all files by default
372 376 if not files:
373 377 files = [self.root]
374 378 dc = self.map.copy()
375 379 else:
376 380 files = util.unique(files)
377 381 dc = self.filterfiles(files)
378 382
379 383 def imatch(file_):
380 384 if file_ not in dc and self.ignore(file_):
381 385 return False
382 386 return match(file_)
383 387
384 388 if ignored: imatch = match
385 389
386 390 # self.root may end with a path separator when self.root == '/'
387 391 common_prefix_len = len(self.root)
388 392 if not self.root.endswith('/'):
389 393 common_prefix_len += 1
390 394 # recursion free walker, faster than os.walk.
391 395 def findfiles(s):
392 396 work = [s]
393 397 while work:
394 398 top = work.pop()
395 399 names = os.listdir(top)
396 400 names.sort()
397 401 # nd is the top of the repository dir tree
398 402 nd = util.normpath(top[common_prefix_len:])
399 403 if nd == '.':
400 404 nd = ''
401 405 else:
402 406 # do not recurse into a repo contained in this
403 407 # one. use bisect to find .hg directory so speed
404 408 # is good on big directory.
405 409 hg = bisect.bisect_left(names, '.hg')
406 410 if hg < len(names) and names[hg] == '.hg':
407 411 if os.path.isdir(os.path.join(top, '.hg')):
408 412 continue
409 413 for f in names:
410 414 np = util.pconvert(os.path.join(nd, f))
411 415 if seen(np):
412 416 continue
413 417 p = os.path.join(top, f)
414 418 # don't trip over symlinks
415 419 st = os.lstat(p)
416 420 if stat.S_ISDIR(st.st_mode):
417 421 ds = util.pconvert(os.path.join(nd, f +'/'))
418 422 if imatch(ds):
419 423 work.append(p)
420 424 if imatch(np) and np in dc:
421 425 yield 'm', np, st
422 426 elif imatch(np):
423 427 if self.supported_type(np, st):
424 428 yield 'f', np, st
425 429 elif np in dc:
426 430 yield 'm', np, st
427 431
428 432 known = {'.hg': 1}
429 433 def seen(fn):
430 434 if fn in known: return True
431 435 known[fn] = 1
432 436
433 437 # step one, find all files that match our criteria
434 438 files.sort()
435 439 for ff in files:
436 440 nf = util.normpath(ff)
437 441 f = self.wjoin(ff)
438 442 try:
439 443 st = os.lstat(f)
440 444 except OSError, inst:
441 445 found = False
442 446 for fn in dc:
443 447 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
444 448 found = True
445 449 break
446 450 if not found:
447 451 if inst.errno != errno.ENOENT or not badmatch:
448 452 self.ui.warn('%s: %s\n' % (
449 453 util.pathto(self.getcwd(), ff),
450 454 inst.strerror))
451 455 elif badmatch and badmatch(ff) and imatch(nf):
452 456 yield 'b', ff, None
453 457 continue
454 458 if stat.S_ISDIR(st.st_mode):
455 459 cmp1 = (lambda x, y: cmp(x[1], y[1]))
456 460 sorted_ = [ x for x in findfiles(f) ]
457 461 sorted_.sort(cmp1)
458 462 for e in sorted_:
459 463 yield e
460 464 else:
461 465 if not seen(nf) and match(nf):
462 466 if self.supported_type(ff, st, verbose=True):
463 467 yield 'f', nf, st
464 468 elif ff in dc:
465 469 yield 'm', nf, st
466 470
467 471 # step two run through anything left in the dc hash and yield
468 472 # if we haven't already seen it
469 473 ks = dc.keys()
470 474 ks.sort()
471 475 for k in ks:
472 476 if not seen(k) and imatch(k):
473 477 yield 'm', k, None
474 478
475 479 def status(self, files=None, match=util.always, list_ignored=False,
476 480 list_clean=False):
477 481 lookup, modified, added, unknown, ignored = [], [], [], [], []
478 482 removed, deleted, clean = [], [], []
479 483
480 484 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
481 485 try:
482 486 type_, mode, size, time = self[fn]
483 487 except KeyError:
484 488 if list_ignored and self.ignore(fn):
485 489 ignored.append(fn)
486 490 else:
487 491 unknown.append(fn)
488 492 continue
489 493 if src == 'm':
490 494 nonexistent = True
491 495 if not st:
492 496 try:
493 497 st = os.lstat(self.wjoin(fn))
494 498 except OSError, inst:
495 499 if inst.errno != errno.ENOENT:
496 500 raise
497 501 st = None
498 502 # We need to re-check that it is a valid file
499 503 if st and self.supported_type(fn, st):
500 504 nonexistent = False
501 505 # XXX: what to do with file no longer present in the fs
502 506 # who are not removed in the dirstate ?
503 507 if nonexistent and type_ in "nm":
504 508 deleted.append(fn)
505 509 continue
506 510 # check the common case first
507 511 if type_ == 'n':
508 512 if not st:
509 513 st = os.lstat(self.wjoin(fn))
510 514 if size >= 0 and (size != st.st_size
511 515 or (mode ^ st.st_mode) & 0100):
512 516 modified.append(fn)
513 517 elif time != int(st.st_mtime):
514 518 lookup.append(fn)
515 519 elif list_clean:
516 520 clean.append(fn)
517 521 elif type_ == 'm':
518 522 modified.append(fn)
519 523 elif type_ == 'a':
520 524 added.append(fn)
521 525 elif type_ == 'r':
522 526 removed.append(fn)
523 527
524 528 return (lookup, modified, added, removed, deleted, unknown, ignored,
525 529 clean)
@@ -1,209 +1,218
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from revlog import *
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 demandload(globals(), "array bisect struct")
12 12 demandload(globals(), "mdiff")
13 13
14 14 class manifestdict(dict):
15 15 def __init__(self, mapping=None, flags=None):
16 16 if mapping is None: mapping = {}
17 17 if flags is None: flags = {}
18 18 dict.__init__(self, mapping)
19 19 self._flags = flags
20 20 def flags(self, f):
21 21 return self._flags.get(f, "")
22 22 def execf(self, f):
23 23 "test for executable in manifest flags"
24 24 return "x" in self.flags(f)
25 25 def linkf(self, f):
26 26 "test for symlink in manifest flags"
27 27 return "l" in self.flags(f)
28 28 def rawset(self, f, entry):
29 29 self[f] = bin(entry[:40])
30 30 fl = entry[40:-1]
31 31 if fl: self._flags[f] = fl
32 32 def set(self, f, execf=False, linkf=False):
33 33 if linkf: self._flags[f] = "l"
34 34 elif execf: self._flags[f] = "x"
35 35 else: self._flags[f] = ""
36 36 def copy(self):
37 37 return manifestdict(dict.copy(self), dict.copy(self._flags))
38 38
39 39 class manifest(revlog):
40 40 def __init__(self, opener, defversion=REVLOGV0):
41 41 self.mapcache = None
42 42 self.listcache = None
43 43 revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
44 44 defversion)
45 45
46 46 def parselines(self, lines):
47 47 for l in lines.splitlines(1):
48 48 yield l.split('\0')
49 49
50 50 def readdelta(self, node):
51 51 delta = mdiff.patchtext(self.delta(node))
52 52 deltamap = manifestdict()
53 53 for f, n in self.parselines(delta):
54 54 deltamap.rawset(f, n)
55 55 return deltamap
56 56
57 57 def read(self, node):
58 58 if node == nullid: return manifestdict() # don't upset local cache
59 59 if self.mapcache and self.mapcache[0] == node:
60 60 return self.mapcache[1]
61 61 text = self.revision(node)
62 62 self.listcache = array.array('c', text)
63 63 mapping = manifestdict()
64 64 for f, n in self.parselines(text):
65 65 mapping.rawset(f, n)
66 66 self.mapcache = (node, mapping)
67 67 return mapping
68 68
69 69 def _search(self, m, s, lo=0, hi=None):
70 70 '''return a tuple (start, end) that says where to find s within m.
71 71
72 72 If the string is found m[start:end] are the line containing
73 73 that string. If start == end the string was not found and
74 74 they indicate the proper sorted insertion point. This was
75 75 taken from bisect_left, and modified to find line start/end as
76 76 it goes along.
77 77
78 78 m should be a buffer or a string
79 79 s is a string'''
80 80 def advance(i, c):
81 81 while i < lenm and m[i] != c:
82 82 i += 1
83 83 return i
84 84 lenm = len(m)
85 85 if not hi:
86 86 hi = lenm
87 87 while lo < hi:
88 88 mid = (lo + hi) // 2
89 89 start = mid
90 90 while start > 0 and m[start-1] != '\n':
91 91 start -= 1
92 92 end = advance(start, '\0')
93 93 if m[start:end] < s:
94 94 # we know that after the null there are 40 bytes of sha1
95 95 # this translates to the bisect lo = mid + 1
96 96 lo = advance(end + 40, '\n') + 1
97 97 else:
98 98 # this translates to the bisect hi = mid
99 99 hi = start
100 100 end = advance(lo, '\0')
101 101 found = m[lo:end]
102 102 if cmp(s, found) == 0:
103 103 # we know that after the null there are 40 bytes of sha1
104 104 end = advance(end + 40, '\n')
105 105 return (lo, end+1)
106 106 else:
107 107 return (lo, lo)
108 108
109 109 def find(self, node, f):
110 110 '''look up entry for a single file efficiently.
111 111 return (node, flag) pair if found, (None, None) if not.'''
112 112 if self.mapcache and node == self.mapcache[0]:
113 113 return self.mapcache[1].get(f), self.mapcache[1].flags(f)
114 114 text = self.revision(node)
115 115 start, end = self._search(text, f)
116 116 if start == end:
117 117 return None, None
118 118 l = text[start:end]
119 119 f, n = l.split('\0')
120 120 return bin(n[:40]), n[40:-1] == 'x'
121 121
122 122 def add(self, map, transaction, link, p1=None, p2=None,
123 123 changed=None):
124 124 # apply the changes collected during the bisect loop to our addlist
125 125 # return a delta suitable for addrevision
126 126 def addlistdelta(addlist, x):
127 127 # start from the bottom up
128 128 # so changes to the offsets don't mess things up.
129 129 i = len(x)
130 130 while i > 0:
131 131 i -= 1
132 132 start = x[i][0]
133 133 end = x[i][1]
134 134 if x[i][2]:
135 135 addlist[start:end] = array.array('c', x[i][2])
136 136 else:
137 137 del addlist[start:end]
138 138 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \
139 139 for d in x ])
140 140
141 def checkforbidden(f):
142 if '\n' in f or '\r' in f:
143 raise RevlogError(_("'\\n' and '\\r' disallowed in filenames"))
144
141 145 # if we're using the listcache, make sure it is valid and
142 146 # parented by the same node we're diffing against
143 147 if not changed or not self.listcache or not p1 or \
144 148 self.mapcache[0] != p1:
145 149 files = map.keys()
146 150 files.sort()
147 151
152 for f in files:
153 checkforbidden(f)
154
148 155 # if this is changed to support newlines in filenames,
149 156 # be sure to check the templates/ dir again (especially *-raw.tmpl)
150 157 text = ["%s\000%s%s\n" % (f, hex(map[f]), map.flags(f)) for f in files]
151 158 self.listcache = array.array('c', "".join(text))
152 159 cachedelta = None
153 160 else:
154 161 addlist = self.listcache
155 162
163 for f in changed[0]:
164 checkforbidden(f)
156 165 # combine the changed lists into one list for sorting
157 166 work = [[x, 0] for x in changed[0]]
158 167 work[len(work):] = [[x, 1] for x in changed[1]]
159 168 work.sort()
160 169
161 170 delta = []
162 171 dstart = None
163 172 dend = None
164 173 dline = [""]
165 174 start = 0
166 175 # zero copy representation of addlist as a buffer
167 176 addbuf = buffer(addlist)
168 177
169 178 # start with a readonly loop that finds the offset of
170 179 # each line and creates the deltas
171 180 for w in work:
172 181 f = w[0]
173 182 # bs will either be the index of the item or the insert point
174 183 start, end = self._search(addbuf, f, start)
175 184 if w[1] == 0:
176 185 l = "%s\000%s%s\n" % (f, hex(map[f]), map.flags(f))
177 186 else:
178 187 l = ""
179 188 if start == end and w[1] == 1:
180 189 # item we want to delete was not found, error out
181 190 raise AssertionError(
182 191 _("failed to remove %s from manifest") % f)
183 192 if dstart != None and dstart <= start and dend >= start:
184 193 if dend < end:
185 194 dend = end
186 195 if l:
187 196 dline.append(l)
188 197 else:
189 198 if dstart != None:
190 199 delta.append([dstart, dend, "".join(dline)])
191 200 dstart = start
192 201 dend = end
193 202 dline = [l]
194 203
195 204 if dstart != None:
196 205 delta.append([dstart, dend, "".join(dline)])
197 206 # apply the delta to the addlist, and get a delta for addrevision
198 207 cachedelta = addlistdelta(addlist, delta)
199 208
200 209 # the delta is only valid if we've been processing the tip revision
201 210 if self.mapcache[0] != self.tip():
202 211 cachedelta = None
203 212 self.listcache = addlist
204 213
205 214 n = self.addrevision(buffer(self.listcache), transaction, link, p1, \
206 215 p2, cachedelta)
207 216 self.mapcache = (n, map)
208 217
209 218 return n
General Comments 0
You need to be logged in to leave comments. Login now