##// END OF EJS Templates
revlog: support writing generaldelta revlogs...
Sune Foldager -
r14270:d6907a56 default
parent child Browse files
Show More
@@ -1,1957 +1,1961
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import bin, hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import repo, changegroup, subrepo, discovery, pushkey
11 11 import changelog, dirstate, filelog, manifest, context, bookmarks
12 12 import lock, transaction, store, encoding
13 13 import scmutil, util, extensions, hook, error
14 14 import match as matchmod
15 15 import merge as mergemod
16 16 import tags as tagsmod
17 17 from lock import release
18 18 import weakref, errno, os, time, inspect
19 19 propertycache = util.propertycache
20 20
21 21 class localrepository(repo.repository):
22 22 capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey',
23 23 'known', 'getbundle'))
24 supportedformats = set(('revlogv1',))
24 supportedformats = set(('revlogv1', 'generaldelta'))
25 25 supported = supportedformats | set(('store', 'fncache', 'shared',
26 26 'dotencode'))
27 27
28 28 def __init__(self, baseui, path=None, create=0):
29 29 repo.repository.__init__(self)
30 30 self.root = os.path.realpath(util.expandpath(path))
31 31 self.path = os.path.join(self.root, ".hg")
32 32 self.origroot = path
33 33 self.auditor = scmutil.pathauditor(self.root, self._checknested)
34 34 self.opener = scmutil.opener(self.path)
35 35 self.wopener = scmutil.opener(self.root)
36 36 self.baseui = baseui
37 37 self.ui = baseui.copy()
38 38
39 39 try:
40 40 self.ui.readconfig(self.join("hgrc"), self.root)
41 41 extensions.loadall(self.ui)
42 42 except IOError:
43 43 pass
44 44
45 45 if not os.path.isdir(self.path):
46 46 if create:
47 47 if not os.path.exists(path):
48 48 util.makedirs(path)
49 49 util.makedir(self.path, notindexed=True)
50 50 requirements = ["revlogv1"]
51 51 if self.ui.configbool('format', 'usestore', True):
52 52 os.mkdir(os.path.join(self.path, "store"))
53 53 requirements.append("store")
54 54 if self.ui.configbool('format', 'usefncache', True):
55 55 requirements.append("fncache")
56 56 if self.ui.configbool('format', 'dotencode', True):
57 57 requirements.append('dotencode')
58 58 # create an invalid changelog
59 59 self.opener.append(
60 60 "00changelog.i",
61 61 '\0\0\0\2' # represents revlogv2
62 62 ' dummy changelog to prevent using the old repo layout'
63 63 )
64 if self.ui.configbool('format', 'generaldelta', False):
65 requirements.append("generaldelta")
64 66 else:
65 67 raise error.RepoError(_("repository %s not found") % path)
66 68 elif create:
67 69 raise error.RepoError(_("repository %s already exists") % path)
68 70 else:
69 71 # find requirements
70 72 requirements = set()
71 73 try:
72 74 requirements = set(self.opener.read("requires").splitlines())
73 75 except IOError, inst:
74 76 if inst.errno != errno.ENOENT:
75 77 raise
76 78 for r in requirements - self.supported:
77 79 raise error.RequirementError(
78 80 _("requirement '%s' not supported") % r)
79 81
80 82 self.sharedpath = self.path
81 83 try:
82 84 s = os.path.realpath(self.opener.read("sharedpath"))
83 85 if not os.path.exists(s):
84 86 raise error.RepoError(
85 87 _('.hg/sharedpath points to nonexistent directory %s') % s)
86 88 self.sharedpath = s
87 89 except IOError, inst:
88 90 if inst.errno != errno.ENOENT:
89 91 raise
90 92
91 93 self.store = store.store(requirements, self.sharedpath, scmutil.opener)
92 94 self.spath = self.store.path
93 95 self.sopener = self.store.opener
94 96 self.sjoin = self.store.join
95 97 self.opener.createmode = self.store.createmode
96 98 self._applyrequirements(requirements)
97 99 if create:
98 100 self._writerequirements()
99 101
100 102 # These two define the set of tags for this repository. _tags
101 103 # maps tag name to node; _tagtypes maps tag name to 'global' or
102 104 # 'local'. (Global tags are defined by .hgtags across all
103 105 # heads, and local tags are defined in .hg/localtags.) They
104 106 # constitute the in-memory cache of tags.
105 107 self._tags = None
106 108 self._tagtypes = None
107 109
108 110 self._branchcache = None
109 111 self._branchcachetip = None
110 112 self.nodetagscache = None
111 113 self.filterpats = {}
112 114 self._datafilters = {}
113 115 self._transref = self._lockref = self._wlockref = None
114 116
115 117 def _applyrequirements(self, requirements):
116 118 self.requirements = requirements
117 119 self.sopener.options = {}
120 if 'generaldelta' in requirements:
121 self.sopener.options['generaldelta'] = 1
118 122
119 123 def _writerequirements(self):
120 124 reqfile = self.opener("requires", "w")
121 125 for r in self.requirements:
122 126 reqfile.write("%s\n" % r)
123 127 reqfile.close()
124 128
125 129 def _checknested(self, path):
126 130 """Determine if path is a legal nested repository."""
127 131 if not path.startswith(self.root):
128 132 return False
129 133 subpath = path[len(self.root) + 1:]
130 134
131 135 # XXX: Checking against the current working copy is wrong in
132 136 # the sense that it can reject things like
133 137 #
134 138 # $ hg cat -r 10 sub/x.txt
135 139 #
136 140 # if sub/ is no longer a subrepository in the working copy
137 141 # parent revision.
138 142 #
139 143 # However, it can of course also allow things that would have
140 144 # been rejected before, such as the above cat command if sub/
141 145 # is a subrepository now, but was a normal directory before.
142 146 # The old path auditor would have rejected by mistake since it
143 147 # panics when it sees sub/.hg/.
144 148 #
145 149 # All in all, checking against the working copy seems sensible
146 150 # since we want to prevent access to nested repositories on
147 151 # the filesystem *now*.
148 152 ctx = self[None]
149 153 parts = util.splitpath(subpath)
150 154 while parts:
151 155 prefix = os.sep.join(parts)
152 156 if prefix in ctx.substate:
153 157 if prefix == subpath:
154 158 return True
155 159 else:
156 160 sub = ctx.sub(prefix)
157 161 return sub.checknested(subpath[len(prefix) + 1:])
158 162 else:
159 163 parts.pop()
160 164 return False
161 165
162 166 @util.propertycache
163 167 def _bookmarks(self):
164 168 return bookmarks.read(self)
165 169
166 170 @util.propertycache
167 171 def _bookmarkcurrent(self):
168 172 return bookmarks.readcurrent(self)
169 173
170 174 @propertycache
171 175 def changelog(self):
172 176 c = changelog.changelog(self.sopener)
173 177 if 'HG_PENDING' in os.environ:
174 178 p = os.environ['HG_PENDING']
175 179 if p.startswith(self.root):
176 180 c.readpending('00changelog.i.a')
177 181 self.sopener.options['defversion'] = c.version
178 182 return c
179 183
180 184 @propertycache
181 185 def manifest(self):
182 186 return manifest.manifest(self.sopener)
183 187
184 188 @propertycache
185 189 def dirstate(self):
186 190 warned = [0]
187 191 def validate(node):
188 192 try:
189 193 self.changelog.rev(node)
190 194 return node
191 195 except error.LookupError:
192 196 if not warned[0]:
193 197 warned[0] = True
194 198 self.ui.warn(_("warning: ignoring unknown"
195 199 " working parent %s!\n") % short(node))
196 200 return nullid
197 201
198 202 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
199 203
200 204 def __getitem__(self, changeid):
201 205 if changeid is None:
202 206 return context.workingctx(self)
203 207 return context.changectx(self, changeid)
204 208
205 209 def __contains__(self, changeid):
206 210 try:
207 211 return bool(self.lookup(changeid))
208 212 except error.RepoLookupError:
209 213 return False
210 214
211 215 def __nonzero__(self):
212 216 return True
213 217
214 218 def __len__(self):
215 219 return len(self.changelog)
216 220
217 221 def __iter__(self):
218 222 for i in xrange(len(self)):
219 223 yield i
220 224
221 225 def url(self):
222 226 return 'file:' + self.root
223 227
224 228 def hook(self, name, throw=False, **args):
225 229 return hook.hook(self.ui, self, name, throw, **args)
226 230
227 231 tag_disallowed = ':\r\n'
228 232
229 233 def _tag(self, names, node, message, local, user, date, extra={}):
230 234 if isinstance(names, str):
231 235 allchars = names
232 236 names = (names,)
233 237 else:
234 238 allchars = ''.join(names)
235 239 for c in self.tag_disallowed:
236 240 if c in allchars:
237 241 raise util.Abort(_('%r cannot be used in a tag name') % c)
238 242
239 243 branches = self.branchmap()
240 244 for name in names:
241 245 self.hook('pretag', throw=True, node=hex(node), tag=name,
242 246 local=local)
243 247 if name in branches:
244 248 self.ui.warn(_("warning: tag %s conflicts with existing"
245 249 " branch name\n") % name)
246 250
247 251 def writetags(fp, names, munge, prevtags):
248 252 fp.seek(0, 2)
249 253 if prevtags and prevtags[-1] != '\n':
250 254 fp.write('\n')
251 255 for name in names:
252 256 m = munge and munge(name) or name
253 257 if self._tagtypes and name in self._tagtypes:
254 258 old = self._tags.get(name, nullid)
255 259 fp.write('%s %s\n' % (hex(old), m))
256 260 fp.write('%s %s\n' % (hex(node), m))
257 261 fp.close()
258 262
259 263 prevtags = ''
260 264 if local:
261 265 try:
262 266 fp = self.opener('localtags', 'r+')
263 267 except IOError:
264 268 fp = self.opener('localtags', 'a')
265 269 else:
266 270 prevtags = fp.read()
267 271
268 272 # local tags are stored in the current charset
269 273 writetags(fp, names, None, prevtags)
270 274 for name in names:
271 275 self.hook('tag', node=hex(node), tag=name, local=local)
272 276 return
273 277
274 278 try:
275 279 fp = self.wfile('.hgtags', 'rb+')
276 280 except IOError:
277 281 fp = self.wfile('.hgtags', 'ab')
278 282 else:
279 283 prevtags = fp.read()
280 284
281 285 # committed tags are stored in UTF-8
282 286 writetags(fp, names, encoding.fromlocal, prevtags)
283 287
284 288 fp.close()
285 289
286 290 if '.hgtags' not in self.dirstate:
287 291 self[None].add(['.hgtags'])
288 292
289 293 m = matchmod.exact(self.root, '', ['.hgtags'])
290 294 tagnode = self.commit(message, user, date, extra=extra, match=m)
291 295
292 296 for name in names:
293 297 self.hook('tag', node=hex(node), tag=name, local=local)
294 298
295 299 return tagnode
296 300
297 301 def tag(self, names, node, message, local, user, date):
298 302 '''tag a revision with one or more symbolic names.
299 303
300 304 names is a list of strings or, when adding a single tag, names may be a
301 305 string.
302 306
303 307 if local is True, the tags are stored in a per-repository file.
304 308 otherwise, they are stored in the .hgtags file, and a new
305 309 changeset is committed with the change.
306 310
307 311 keyword arguments:
308 312
309 313 local: whether to store tags in non-version-controlled file
310 314 (default False)
311 315
312 316 message: commit message to use if committing
313 317
314 318 user: name of user to use if committing
315 319
316 320 date: date tuple to use if committing'''
317 321
318 322 if not local:
319 323 for x in self.status()[:5]:
320 324 if '.hgtags' in x:
321 325 raise util.Abort(_('working copy of .hgtags is changed '
322 326 '(please commit .hgtags manually)'))
323 327
324 328 self.tags() # instantiate the cache
325 329 self._tag(names, node, message, local, user, date)
326 330
327 331 def tags(self):
328 332 '''return a mapping of tag to node'''
329 333 if self._tags is None:
330 334 (self._tags, self._tagtypes) = self._findtags()
331 335
332 336 return self._tags
333 337
334 338 def _findtags(self):
335 339 '''Do the hard work of finding tags. Return a pair of dicts
336 340 (tags, tagtypes) where tags maps tag name to node, and tagtypes
337 341 maps tag name to a string like \'global\' or \'local\'.
338 342 Subclasses or extensions are free to add their own tags, but
339 343 should be aware that the returned dicts will be retained for the
340 344 duration of the localrepo object.'''
341 345
342 346 # XXX what tagtype should subclasses/extensions use? Currently
343 347 # mq and bookmarks add tags, but do not set the tagtype at all.
344 348 # Should each extension invent its own tag type? Should there
345 349 # be one tagtype for all such "virtual" tags? Or is the status
346 350 # quo fine?
347 351
348 352 alltags = {} # map tag name to (node, hist)
349 353 tagtypes = {}
350 354
351 355 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
352 356 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
353 357
354 358 # Build the return dicts. Have to re-encode tag names because
355 359 # the tags module always uses UTF-8 (in order not to lose info
356 360 # writing to the cache), but the rest of Mercurial wants them in
357 361 # local encoding.
358 362 tags = {}
359 363 for (name, (node, hist)) in alltags.iteritems():
360 364 if node != nullid:
361 365 try:
362 366 # ignore tags to unknown nodes
363 367 self.changelog.lookup(node)
364 368 tags[encoding.tolocal(name)] = node
365 369 except error.LookupError:
366 370 pass
367 371 tags['tip'] = self.changelog.tip()
368 372 tagtypes = dict([(encoding.tolocal(name), value)
369 373 for (name, value) in tagtypes.iteritems()])
370 374 return (tags, tagtypes)
371 375
372 376 def tagtype(self, tagname):
373 377 '''
374 378 return the type of the given tag. result can be:
375 379
376 380 'local' : a local tag
377 381 'global' : a global tag
378 382 None : tag does not exist
379 383 '''
380 384
381 385 self.tags()
382 386
383 387 return self._tagtypes.get(tagname)
384 388
385 389 def tagslist(self):
386 390 '''return a list of tags ordered by revision'''
387 391 l = []
388 392 for t, n in self.tags().iteritems():
389 393 r = self.changelog.rev(n)
390 394 l.append((r, t, n))
391 395 return [(t, n) for r, t, n in sorted(l)]
392 396
393 397 def nodetags(self, node):
394 398 '''return the tags associated with a node'''
395 399 if not self.nodetagscache:
396 400 self.nodetagscache = {}
397 401 for t, n in self.tags().iteritems():
398 402 self.nodetagscache.setdefault(n, []).append(t)
399 403 for tags in self.nodetagscache.itervalues():
400 404 tags.sort()
401 405 return self.nodetagscache.get(node, [])
402 406
403 407 def nodebookmarks(self, node):
404 408 marks = []
405 409 for bookmark, n in self._bookmarks.iteritems():
406 410 if n == node:
407 411 marks.append(bookmark)
408 412 return sorted(marks)
409 413
410 414 def _branchtags(self, partial, lrev):
411 415 # TODO: rename this function?
412 416 tiprev = len(self) - 1
413 417 if lrev != tiprev:
414 418 ctxgen = (self[r] for r in xrange(lrev + 1, tiprev + 1))
415 419 self._updatebranchcache(partial, ctxgen)
416 420 self._writebranchcache(partial, self.changelog.tip(), tiprev)
417 421
418 422 return partial
419 423
420 424 def updatebranchcache(self):
421 425 tip = self.changelog.tip()
422 426 if self._branchcache is not None and self._branchcachetip == tip:
423 427 return self._branchcache
424 428
425 429 oldtip = self._branchcachetip
426 430 self._branchcachetip = tip
427 431 if oldtip is None or oldtip not in self.changelog.nodemap:
428 432 partial, last, lrev = self._readbranchcache()
429 433 else:
430 434 lrev = self.changelog.rev(oldtip)
431 435 partial = self._branchcache
432 436
433 437 self._branchtags(partial, lrev)
434 438 # this private cache holds all heads (not just tips)
435 439 self._branchcache = partial
436 440
437 441 def branchmap(self):
438 442 '''returns a dictionary {branch: [branchheads]}'''
439 443 self.updatebranchcache()
440 444 return self._branchcache
441 445
442 446 def branchtags(self):
443 447 '''return a dict where branch names map to the tipmost head of
444 448 the branch, open heads come before closed'''
445 449 bt = {}
446 450 for bn, heads in self.branchmap().iteritems():
447 451 tip = heads[-1]
448 452 for h in reversed(heads):
449 453 if 'close' not in self.changelog.read(h)[5]:
450 454 tip = h
451 455 break
452 456 bt[bn] = tip
453 457 return bt
454 458
455 459 def _readbranchcache(self):
456 460 partial = {}
457 461 try:
458 462 f = self.opener("cache/branchheads")
459 463 lines = f.read().split('\n')
460 464 f.close()
461 465 except (IOError, OSError):
462 466 return {}, nullid, nullrev
463 467
464 468 try:
465 469 last, lrev = lines.pop(0).split(" ", 1)
466 470 last, lrev = bin(last), int(lrev)
467 471 if lrev >= len(self) or self[lrev].node() != last:
468 472 # invalidate the cache
469 473 raise ValueError('invalidating branch cache (tip differs)')
470 474 for l in lines:
471 475 if not l:
472 476 continue
473 477 node, label = l.split(" ", 1)
474 478 label = encoding.tolocal(label.strip())
475 479 partial.setdefault(label, []).append(bin(node))
476 480 except KeyboardInterrupt:
477 481 raise
478 482 except Exception, inst:
479 483 if self.ui.debugflag:
480 484 self.ui.warn(str(inst), '\n')
481 485 partial, last, lrev = {}, nullid, nullrev
482 486 return partial, last, lrev
483 487
484 488 def _writebranchcache(self, branches, tip, tiprev):
485 489 try:
486 490 f = self.opener("cache/branchheads", "w", atomictemp=True)
487 491 f.write("%s %s\n" % (hex(tip), tiprev))
488 492 for label, nodes in branches.iteritems():
489 493 for node in nodes:
490 494 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
491 495 f.rename()
492 496 except (IOError, OSError):
493 497 pass
494 498
495 499 def _updatebranchcache(self, partial, ctxgen):
496 500 # collect new branch entries
497 501 newbranches = {}
498 502 for c in ctxgen:
499 503 newbranches.setdefault(c.branch(), []).append(c.node())
500 504 # if older branchheads are reachable from new ones, they aren't
501 505 # really branchheads. Note checking parents is insufficient:
502 506 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
503 507 for branch, newnodes in newbranches.iteritems():
504 508 bheads = partial.setdefault(branch, [])
505 509 bheads.extend(newnodes)
506 510 if len(bheads) <= 1:
507 511 continue
508 512 bheads = sorted(bheads, key=lambda x: self[x].rev())
509 513 # starting from tip means fewer passes over reachable
510 514 while newnodes:
511 515 latest = newnodes.pop()
512 516 if latest not in bheads:
513 517 continue
514 518 minbhrev = self[bheads[0]].node()
515 519 reachable = self.changelog.reachable(latest, minbhrev)
516 520 reachable.remove(latest)
517 521 if reachable:
518 522 bheads = [b for b in bheads if b not in reachable]
519 523 partial[branch] = bheads
520 524
521 525 def lookup(self, key):
522 526 if isinstance(key, int):
523 527 return self.changelog.node(key)
524 528 elif key == '.':
525 529 return self.dirstate.p1()
526 530 elif key == 'null':
527 531 return nullid
528 532 elif key == 'tip':
529 533 return self.changelog.tip()
530 534 n = self.changelog._match(key)
531 535 if n:
532 536 return n
533 537 if key in self._bookmarks:
534 538 return self._bookmarks[key]
535 539 if key in self.tags():
536 540 return self.tags()[key]
537 541 if key in self.branchtags():
538 542 return self.branchtags()[key]
539 543 n = self.changelog._partialmatch(key)
540 544 if n:
541 545 return n
542 546
543 547 # can't find key, check if it might have come from damaged dirstate
544 548 if key in self.dirstate.parents():
545 549 raise error.Abort(_("working directory has unknown parent '%s'!")
546 550 % short(key))
547 551 try:
548 552 if len(key) == 20:
549 553 key = hex(key)
550 554 except TypeError:
551 555 pass
552 556 raise error.RepoLookupError(_("unknown revision '%s'") % key)
553 557
554 558 def lookupbranch(self, key, remote=None):
555 559 repo = remote or self
556 560 if key in repo.branchmap():
557 561 return key
558 562
559 563 repo = (remote and remote.local()) and remote or self
560 564 return repo[key].branch()
561 565
562 566 def known(self, nodes):
563 567 nm = self.changelog.nodemap
564 568 return [(n in nm) for n in nodes]
565 569
566 570 def local(self):
567 571 return True
568 572
569 573 def join(self, f):
570 574 return os.path.join(self.path, f)
571 575
572 576 def wjoin(self, f):
573 577 return os.path.join(self.root, f)
574 578
575 579 def file(self, f):
576 580 if f[0] == '/':
577 581 f = f[1:]
578 582 return filelog.filelog(self.sopener, f)
579 583
580 584 def changectx(self, changeid):
581 585 return self[changeid]
582 586
583 587 def parents(self, changeid=None):
584 588 '''get list of changectxs for parents of changeid'''
585 589 return self[changeid].parents()
586 590
587 591 def filectx(self, path, changeid=None, fileid=None):
588 592 """changeid can be a changeset revision, node, or tag.
589 593 fileid can be a file revision or node."""
590 594 return context.filectx(self, path, changeid, fileid)
591 595
592 596 def getcwd(self):
593 597 return self.dirstate.getcwd()
594 598
595 599 def pathto(self, f, cwd=None):
596 600 return self.dirstate.pathto(f, cwd)
597 601
598 602 def wfile(self, f, mode='r'):
599 603 return self.wopener(f, mode)
600 604
601 605 def _link(self, f):
602 606 return os.path.islink(self.wjoin(f))
603 607
604 608 def _loadfilter(self, filter):
605 609 if filter not in self.filterpats:
606 610 l = []
607 611 for pat, cmd in self.ui.configitems(filter):
608 612 if cmd == '!':
609 613 continue
610 614 mf = matchmod.match(self.root, '', [pat])
611 615 fn = None
612 616 params = cmd
613 617 for name, filterfn in self._datafilters.iteritems():
614 618 if cmd.startswith(name):
615 619 fn = filterfn
616 620 params = cmd[len(name):].lstrip()
617 621 break
618 622 if not fn:
619 623 fn = lambda s, c, **kwargs: util.filter(s, c)
620 624 # Wrap old filters not supporting keyword arguments
621 625 if not inspect.getargspec(fn)[2]:
622 626 oldfn = fn
623 627 fn = lambda s, c, **kwargs: oldfn(s, c)
624 628 l.append((mf, fn, params))
625 629 self.filterpats[filter] = l
626 630 return self.filterpats[filter]
627 631
628 632 def _filter(self, filterpats, filename, data):
629 633 for mf, fn, cmd in filterpats:
630 634 if mf(filename):
631 635 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
632 636 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
633 637 break
634 638
635 639 return data
636 640
637 641 @propertycache
638 642 def _encodefilterpats(self):
639 643 return self._loadfilter('encode')
640 644
641 645 @propertycache
642 646 def _decodefilterpats(self):
643 647 return self._loadfilter('decode')
644 648
645 649 def adddatafilter(self, name, filter):
646 650 self._datafilters[name] = filter
647 651
648 652 def wread(self, filename):
649 653 if self._link(filename):
650 654 data = os.readlink(self.wjoin(filename))
651 655 else:
652 656 data = self.wopener.read(filename)
653 657 return self._filter(self._encodefilterpats, filename, data)
654 658
655 659 def wwrite(self, filename, data, flags):
656 660 data = self._filter(self._decodefilterpats, filename, data)
657 661 if 'l' in flags:
658 662 self.wopener.symlink(data, filename)
659 663 else:
660 664 self.wopener.write(filename, data)
661 665 if 'x' in flags:
662 666 util.setflags(self.wjoin(filename), False, True)
663 667
664 668 def wwritedata(self, filename, data):
665 669 return self._filter(self._decodefilterpats, filename, data)
666 670
667 671 def transaction(self, desc):
668 672 tr = self._transref and self._transref() or None
669 673 if tr and tr.running():
670 674 return tr.nest()
671 675
672 676 # abort here if the journal already exists
673 677 if os.path.exists(self.sjoin("journal")):
674 678 raise error.RepoError(
675 679 _("abandoned transaction found - run hg recover"))
676 680
677 681 # save dirstate for rollback
678 682 try:
679 683 ds = self.opener.read("dirstate")
680 684 except IOError:
681 685 ds = ""
682 686 self.opener.write("journal.dirstate", ds)
683 687 self.opener.write("journal.branch",
684 688 encoding.fromlocal(self.dirstate.branch()))
685 689 self.opener.write("journal.desc",
686 690 "%d\n%s\n" % (len(self), desc))
687 691
688 692 renames = [(self.sjoin("journal"), self.sjoin("undo")),
689 693 (self.join("journal.dirstate"), self.join("undo.dirstate")),
690 694 (self.join("journal.branch"), self.join("undo.branch")),
691 695 (self.join("journal.desc"), self.join("undo.desc"))]
692 696 tr = transaction.transaction(self.ui.warn, self.sopener,
693 697 self.sjoin("journal"),
694 698 aftertrans(renames),
695 699 self.store.createmode)
696 700 self._transref = weakref.ref(tr)
697 701 return tr
698 702
699 703 def recover(self):
700 704 lock = self.lock()
701 705 try:
702 706 if os.path.exists(self.sjoin("journal")):
703 707 self.ui.status(_("rolling back interrupted transaction\n"))
704 708 transaction.rollback(self.sopener, self.sjoin("journal"),
705 709 self.ui.warn)
706 710 self.invalidate()
707 711 return True
708 712 else:
709 713 self.ui.warn(_("no interrupted transaction available\n"))
710 714 return False
711 715 finally:
712 716 lock.release()
713 717
714 718 def rollback(self, dryrun=False):
715 719 wlock = lock = None
716 720 try:
717 721 wlock = self.wlock()
718 722 lock = self.lock()
719 723 if os.path.exists(self.sjoin("undo")):
720 724 try:
721 725 args = self.opener.read("undo.desc").splitlines()
722 726 if len(args) >= 3 and self.ui.verbose:
723 727 desc = _("repository tip rolled back to revision %s"
724 728 " (undo %s: %s)\n") % (
725 729 int(args[0]) - 1, args[1], args[2])
726 730 elif len(args) >= 2:
727 731 desc = _("repository tip rolled back to revision %s"
728 732 " (undo %s)\n") % (
729 733 int(args[0]) - 1, args[1])
730 734 except IOError:
731 735 desc = _("rolling back unknown transaction\n")
732 736 self.ui.status(desc)
733 737 if dryrun:
734 738 return
735 739 transaction.rollback(self.sopener, self.sjoin("undo"),
736 740 self.ui.warn)
737 741 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
738 742 if os.path.exists(self.join('undo.bookmarks')):
739 743 util.rename(self.join('undo.bookmarks'),
740 744 self.join('bookmarks'))
741 745 try:
742 746 branch = self.opener.read("undo.branch")
743 747 self.dirstate.setbranch(branch)
744 748 except IOError:
745 749 self.ui.warn(_("named branch could not be reset, "
746 750 "current branch is still: %s\n")
747 751 % self.dirstate.branch())
748 752 self.invalidate()
749 753 self.dirstate.invalidate()
750 754 self.destroyed()
751 755 parents = tuple([p.rev() for p in self.parents()])
752 756 if len(parents) > 1:
753 757 self.ui.status(_("working directory now based on "
754 758 "revisions %d and %d\n") % parents)
755 759 else:
756 760 self.ui.status(_("working directory now based on "
757 761 "revision %d\n") % parents)
758 762 else:
759 763 self.ui.warn(_("no rollback information available\n"))
760 764 return 1
761 765 finally:
762 766 release(lock, wlock)
763 767
764 768 def invalidatecaches(self):
765 769 self._tags = None
766 770 self._tagtypes = None
767 771 self.nodetagscache = None
768 772 self._branchcache = None # in UTF-8
769 773 self._branchcachetip = None
770 774
771 775 def invalidate(self):
772 776 for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"):
773 777 if a in self.__dict__:
774 778 delattr(self, a)
775 779 self.invalidatecaches()
776 780
777 781 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
778 782 try:
779 783 l = lock.lock(lockname, 0, releasefn, desc=desc)
780 784 except error.LockHeld, inst:
781 785 if not wait:
782 786 raise
783 787 self.ui.warn(_("waiting for lock on %s held by %r\n") %
784 788 (desc, inst.locker))
785 789 # default to 600 seconds timeout
786 790 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
787 791 releasefn, desc=desc)
788 792 if acquirefn:
789 793 acquirefn()
790 794 return l
791 795
792 796 def lock(self, wait=True):
793 797 '''Lock the repository store (.hg/store) and return a weak reference
794 798 to the lock. Use this before modifying the store (e.g. committing or
795 799 stripping). If you are opening a transaction, get a lock as well.)'''
796 800 l = self._lockref and self._lockref()
797 801 if l is not None and l.held:
798 802 l.lock()
799 803 return l
800 804
801 805 l = self._lock(self.sjoin("lock"), wait, self.store.write,
802 806 self.invalidate, _('repository %s') % self.origroot)
803 807 self._lockref = weakref.ref(l)
804 808 return l
805 809
806 810 def wlock(self, wait=True):
807 811 '''Lock the non-store parts of the repository (everything under
808 812 .hg except .hg/store) and return a weak reference to the lock.
809 813 Use this before modifying files in .hg.'''
810 814 l = self._wlockref and self._wlockref()
811 815 if l is not None and l.held:
812 816 l.lock()
813 817 return l
814 818
815 819 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
816 820 self.dirstate.invalidate, _('working directory of %s') %
817 821 self.origroot)
818 822 self._wlockref = weakref.ref(l)
819 823 return l
820 824
821 825 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
822 826 """
823 827 commit an individual file as part of a larger transaction
824 828 """
825 829
826 830 fname = fctx.path()
827 831 text = fctx.data()
828 832 flog = self.file(fname)
829 833 fparent1 = manifest1.get(fname, nullid)
830 834 fparent2 = fparent2o = manifest2.get(fname, nullid)
831 835
832 836 meta = {}
833 837 copy = fctx.renamed()
834 838 if copy and copy[0] != fname:
835 839 # Mark the new revision of this file as a copy of another
836 840 # file. This copy data will effectively act as a parent
837 841 # of this new revision. If this is a merge, the first
838 842 # parent will be the nullid (meaning "look up the copy data")
839 843 # and the second one will be the other parent. For example:
840 844 #
841 845 # 0 --- 1 --- 3 rev1 changes file foo
842 846 # \ / rev2 renames foo to bar and changes it
843 847 # \- 2 -/ rev3 should have bar with all changes and
844 848 # should record that bar descends from
845 849 # bar in rev2 and foo in rev1
846 850 #
847 851 # this allows this merge to succeed:
848 852 #
849 853 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
850 854 # \ / merging rev3 and rev4 should use bar@rev2
851 855 # \- 2 --- 4 as the merge base
852 856 #
853 857
854 858 cfname = copy[0]
855 859 crev = manifest1.get(cfname)
856 860 newfparent = fparent2
857 861
858 862 if manifest2: # branch merge
859 863 if fparent2 == nullid or crev is None: # copied on remote side
860 864 if cfname in manifest2:
861 865 crev = manifest2[cfname]
862 866 newfparent = fparent1
863 867
864 868 # find source in nearest ancestor if we've lost track
865 869 if not crev:
866 870 self.ui.debug(" %s: searching for copy revision for %s\n" %
867 871 (fname, cfname))
868 872 for ancestor in self[None].ancestors():
869 873 if cfname in ancestor:
870 874 crev = ancestor[cfname].filenode()
871 875 break
872 876
873 877 if crev:
874 878 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
875 879 meta["copy"] = cfname
876 880 meta["copyrev"] = hex(crev)
877 881 fparent1, fparent2 = nullid, newfparent
878 882 else:
879 883 self.ui.warn(_("warning: can't find ancestor for '%s' "
880 884 "copied from '%s'!\n") % (fname, cfname))
881 885
882 886 elif fparent2 != nullid:
883 887 # is one parent an ancestor of the other?
884 888 fparentancestor = flog.ancestor(fparent1, fparent2)
885 889 if fparentancestor == fparent1:
886 890 fparent1, fparent2 = fparent2, nullid
887 891 elif fparentancestor == fparent2:
888 892 fparent2 = nullid
889 893
890 894 # is the file changed?
891 895 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
892 896 changelist.append(fname)
893 897 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
894 898
895 899 # are just the flags changed during merge?
896 900 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
897 901 changelist.append(fname)
898 902
899 903 return fparent1
900 904
901 905 def commit(self, text="", user=None, date=None, match=None, force=False,
902 906 editor=False, extra={}):
903 907 """Add a new revision to current repository.
904 908
905 909 Revision information is gathered from the working directory,
906 910 match can be used to filter the committed files. If editor is
907 911 supplied, it is called to get a commit message.
908 912 """
909 913
910 914 def fail(f, msg):
911 915 raise util.Abort('%s: %s' % (f, msg))
912 916
913 917 if not match:
914 918 match = matchmod.always(self.root, '')
915 919
916 920 if not force:
917 921 vdirs = []
918 922 match.dir = vdirs.append
919 923 match.bad = fail
920 924
921 925 wlock = self.wlock()
922 926 try:
923 927 wctx = self[None]
924 928 merge = len(wctx.parents()) > 1
925 929
926 930 if (not force and merge and match and
927 931 (match.files() or match.anypats())):
928 932 raise util.Abort(_('cannot partially commit a merge '
929 933 '(do not specify files or patterns)'))
930 934
931 935 changes = self.status(match=match, clean=force)
932 936 if force:
933 937 changes[0].extend(changes[6]) # mq may commit unchanged files
934 938
935 939 # check subrepos
936 940 subs = []
937 941 removedsubs = set()
938 942 for p in wctx.parents():
939 943 removedsubs.update(s for s in p.substate if match(s))
940 944 for s in wctx.substate:
941 945 removedsubs.discard(s)
942 946 if match(s) and wctx.sub(s).dirty():
943 947 subs.append(s)
944 948 if (subs or removedsubs):
945 949 if (not match('.hgsub') and
946 950 '.hgsub' in (wctx.modified() + wctx.added())):
947 951 raise util.Abort(_("can't commit subrepos without .hgsub"))
948 952 if '.hgsubstate' not in changes[0]:
949 953 changes[0].insert(0, '.hgsubstate')
950 954
951 955 if subs and not self.ui.configbool('ui', 'commitsubrepos', True):
952 956 changedsubs = [s for s in subs if wctx.sub(s).dirty(True)]
953 957 if changedsubs:
954 958 raise util.Abort(_("uncommitted changes in subrepo %s")
955 959 % changedsubs[0])
956 960
957 961 # make sure all explicit patterns are matched
958 962 if not force and match.files():
959 963 matched = set(changes[0] + changes[1] + changes[2])
960 964
961 965 for f in match.files():
962 966 if f == '.' or f in matched or f in wctx.substate:
963 967 continue
964 968 if f in changes[3]: # missing
965 969 fail(f, _('file not found!'))
966 970 if f in vdirs: # visited directory
967 971 d = f + '/'
968 972 for mf in matched:
969 973 if mf.startswith(d):
970 974 break
971 975 else:
972 976 fail(f, _("no match under directory!"))
973 977 elif f not in self.dirstate:
974 978 fail(f, _("file not tracked!"))
975 979
976 980 if (not force and not extra.get("close") and not merge
977 981 and not (changes[0] or changes[1] or changes[2])
978 982 and wctx.branch() == wctx.p1().branch()):
979 983 return None
980 984
981 985 ms = mergemod.mergestate(self)
982 986 for f in changes[0]:
983 987 if f in ms and ms[f] == 'u':
984 988 raise util.Abort(_("unresolved merge conflicts "
985 989 "(see hg help resolve)"))
986 990
987 991 cctx = context.workingctx(self, text, user, date, extra, changes)
988 992 if editor:
989 993 cctx._text = editor(self, cctx, subs)
990 994 edited = (text != cctx._text)
991 995
992 996 # commit subs
993 997 if subs or removedsubs:
994 998 state = wctx.substate.copy()
995 999 for s in sorted(subs):
996 1000 sub = wctx.sub(s)
997 1001 self.ui.status(_('committing subrepository %s\n') %
998 1002 subrepo.subrelpath(sub))
999 1003 sr = sub.commit(cctx._text, user, date)
1000 1004 state[s] = (state[s][0], sr)
1001 1005 subrepo.writestate(self, state)
1002 1006
1003 1007 # Save commit message in case this transaction gets rolled back
1004 1008 # (e.g. by a pretxncommit hook). Leave the content alone on
1005 1009 # the assumption that the user will use the same editor again.
1006 1010 msgfile = self.opener('last-message.txt', 'wb')
1007 1011 msgfile.write(cctx._text)
1008 1012 msgfile.close()
1009 1013
1010 1014 p1, p2 = self.dirstate.parents()
1011 1015 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1012 1016 try:
1013 1017 self.hook("precommit", throw=True, parent1=hookp1, parent2=hookp2)
1014 1018 ret = self.commitctx(cctx, True)
1015 1019 except:
1016 1020 if edited:
1017 1021 msgfn = self.pathto(msgfile.name[len(self.root)+1:])
1018 1022 self.ui.write(
1019 1023 _('note: commit message saved in %s\n') % msgfn)
1020 1024 raise
1021 1025
1022 1026 # update bookmarks, dirstate and mergestate
1023 1027 bookmarks.update(self, p1, ret)
1024 1028 for f in changes[0] + changes[1]:
1025 1029 self.dirstate.normal(f)
1026 1030 for f in changes[2]:
1027 1031 self.dirstate.forget(f)
1028 1032 self.dirstate.setparents(ret)
1029 1033 ms.reset()
1030 1034 finally:
1031 1035 wlock.release()
1032 1036
1033 1037 self.hook("commit", node=hex(ret), parent1=hookp1, parent2=hookp2)
1034 1038 return ret
1035 1039
1036 1040 def commitctx(self, ctx, error=False):
1037 1041 """Add a new revision to current repository.
1038 1042 Revision information is passed via the context argument.
1039 1043 """
1040 1044
1041 1045 tr = lock = None
1042 1046 removed = list(ctx.removed())
1043 1047 p1, p2 = ctx.p1(), ctx.p2()
1044 1048 user = ctx.user()
1045 1049
1046 1050 lock = self.lock()
1047 1051 try:
1048 1052 tr = self.transaction("commit")
1049 1053 trp = weakref.proxy(tr)
1050 1054
1051 1055 if ctx.files():
1052 1056 m1 = p1.manifest().copy()
1053 1057 m2 = p2.manifest()
1054 1058
1055 1059 # check in files
1056 1060 new = {}
1057 1061 changed = []
1058 1062 linkrev = len(self)
1059 1063 for f in sorted(ctx.modified() + ctx.added()):
1060 1064 self.ui.note(f + "\n")
1061 1065 try:
1062 1066 fctx = ctx[f]
1063 1067 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1064 1068 changed)
1065 1069 m1.set(f, fctx.flags())
1066 1070 except OSError, inst:
1067 1071 self.ui.warn(_("trouble committing %s!\n") % f)
1068 1072 raise
1069 1073 except IOError, inst:
1070 1074 errcode = getattr(inst, 'errno', errno.ENOENT)
1071 1075 if error or errcode and errcode != errno.ENOENT:
1072 1076 self.ui.warn(_("trouble committing %s!\n") % f)
1073 1077 raise
1074 1078 else:
1075 1079 removed.append(f)
1076 1080
1077 1081 # update manifest
1078 1082 m1.update(new)
1079 1083 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1080 1084 drop = [f for f in removed if f in m1]
1081 1085 for f in drop:
1082 1086 del m1[f]
1083 1087 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1084 1088 p2.manifestnode(), (new, drop))
1085 1089 files = changed + removed
1086 1090 else:
1087 1091 mn = p1.manifestnode()
1088 1092 files = []
1089 1093
1090 1094 # update changelog
1091 1095 self.changelog.delayupdate()
1092 1096 n = self.changelog.add(mn, files, ctx.description(),
1093 1097 trp, p1.node(), p2.node(),
1094 1098 user, ctx.date(), ctx.extra().copy())
1095 1099 p = lambda: self.changelog.writepending() and self.root or ""
1096 1100 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1097 1101 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1098 1102 parent2=xp2, pending=p)
1099 1103 self.changelog.finalize(trp)
1100 1104 tr.close()
1101 1105
1102 1106 if self._branchcache:
1103 1107 self.updatebranchcache()
1104 1108 return n
1105 1109 finally:
1106 1110 if tr:
1107 1111 tr.release()
1108 1112 lock.release()
1109 1113
1110 1114 def destroyed(self):
1111 1115 '''Inform the repository that nodes have been destroyed.
1112 1116 Intended for use by strip and rollback, so there's a common
1113 1117 place for anything that has to be done after destroying history.'''
1114 1118 # XXX it might be nice if we could take the list of destroyed
1115 1119 # nodes, but I don't see an easy way for rollback() to do that
1116 1120
1117 1121 # Ensure the persistent tag cache is updated. Doing it now
1118 1122 # means that the tag cache only has to worry about destroyed
1119 1123 # heads immediately after a strip/rollback. That in turn
1120 1124 # guarantees that "cachetip == currenttip" (comparing both rev
1121 1125 # and node) always means no nodes have been added or destroyed.
1122 1126
1123 1127 # XXX this is suboptimal when qrefresh'ing: we strip the current
1124 1128 # head, refresh the tag cache, then immediately add a new head.
1125 1129 # But I think doing it this way is necessary for the "instant
1126 1130 # tag cache retrieval" case to work.
1127 1131 self.invalidatecaches()
1128 1132
1129 1133 def walk(self, match, node=None):
1130 1134 '''
1131 1135 walk recursively through the directory tree or a given
1132 1136 changeset, finding all files matched by the match
1133 1137 function
1134 1138 '''
1135 1139 return self[node].walk(match)
1136 1140
1137 1141 def status(self, node1='.', node2=None, match=None,
1138 1142 ignored=False, clean=False, unknown=False,
1139 1143 listsubrepos=False):
1140 1144 """return status of files between two nodes or node and working directory
1141 1145
1142 1146 If node1 is None, use the first dirstate parent instead.
1143 1147 If node2 is None, compare node1 with working directory.
1144 1148 """
1145 1149
1146 1150 def mfmatches(ctx):
1147 1151 mf = ctx.manifest().copy()
1148 1152 for fn in mf.keys():
1149 1153 if not match(fn):
1150 1154 del mf[fn]
1151 1155 return mf
1152 1156
1153 1157 if isinstance(node1, context.changectx):
1154 1158 ctx1 = node1
1155 1159 else:
1156 1160 ctx1 = self[node1]
1157 1161 if isinstance(node2, context.changectx):
1158 1162 ctx2 = node2
1159 1163 else:
1160 1164 ctx2 = self[node2]
1161 1165
1162 1166 working = ctx2.rev() is None
1163 1167 parentworking = working and ctx1 == self['.']
1164 1168 match = match or matchmod.always(self.root, self.getcwd())
1165 1169 listignored, listclean, listunknown = ignored, clean, unknown
1166 1170
1167 1171 # load earliest manifest first for caching reasons
1168 1172 if not working and ctx2.rev() < ctx1.rev():
1169 1173 ctx2.manifest()
1170 1174
1171 1175 if not parentworking:
1172 1176 def bad(f, msg):
1173 1177 if f not in ctx1:
1174 1178 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1175 1179 match.bad = bad
1176 1180
1177 1181 if working: # we need to scan the working dir
1178 1182 subrepos = []
1179 1183 if '.hgsub' in self.dirstate:
1180 1184 subrepos = ctx1.substate.keys()
1181 1185 s = self.dirstate.status(match, subrepos, listignored,
1182 1186 listclean, listunknown)
1183 1187 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1184 1188
1185 1189 # check for any possibly clean files
1186 1190 if parentworking and cmp:
1187 1191 fixup = []
1188 1192 # do a full compare of any files that might have changed
1189 1193 for f in sorted(cmp):
1190 1194 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1191 1195 or ctx1[f].cmp(ctx2[f])):
1192 1196 modified.append(f)
1193 1197 else:
1194 1198 fixup.append(f)
1195 1199
1196 1200 # update dirstate for files that are actually clean
1197 1201 if fixup:
1198 1202 if listclean:
1199 1203 clean += fixup
1200 1204
1201 1205 try:
1202 1206 # updating the dirstate is optional
1203 1207 # so we don't wait on the lock
1204 1208 wlock = self.wlock(False)
1205 1209 try:
1206 1210 for f in fixup:
1207 1211 self.dirstate.normal(f)
1208 1212 finally:
1209 1213 wlock.release()
1210 1214 except error.LockError:
1211 1215 pass
1212 1216
1213 1217 if not parentworking:
1214 1218 mf1 = mfmatches(ctx1)
1215 1219 if working:
1216 1220 # we are comparing working dir against non-parent
1217 1221 # generate a pseudo-manifest for the working dir
1218 1222 mf2 = mfmatches(self['.'])
1219 1223 for f in cmp + modified + added:
1220 1224 mf2[f] = None
1221 1225 mf2.set(f, ctx2.flags(f))
1222 1226 for f in removed:
1223 1227 if f in mf2:
1224 1228 del mf2[f]
1225 1229 else:
1226 1230 # we are comparing two revisions
1227 1231 deleted, unknown, ignored = [], [], []
1228 1232 mf2 = mfmatches(ctx2)
1229 1233
1230 1234 modified, added, clean = [], [], []
1231 1235 for fn in mf2:
1232 1236 if fn in mf1:
1233 1237 if (fn not in deleted and
1234 1238 (mf1.flags(fn) != mf2.flags(fn) or
1235 1239 (mf1[fn] != mf2[fn] and
1236 1240 (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
1237 1241 modified.append(fn)
1238 1242 elif listclean:
1239 1243 clean.append(fn)
1240 1244 del mf1[fn]
1241 1245 elif fn not in deleted:
1242 1246 added.append(fn)
1243 1247 removed = mf1.keys()
1244 1248
1245 1249 r = modified, added, removed, deleted, unknown, ignored, clean
1246 1250
1247 1251 if listsubrepos:
1248 1252 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1249 1253 if working:
1250 1254 rev2 = None
1251 1255 else:
1252 1256 rev2 = ctx2.substate[subpath][1]
1253 1257 try:
1254 1258 submatch = matchmod.narrowmatcher(subpath, match)
1255 1259 s = sub.status(rev2, match=submatch, ignored=listignored,
1256 1260 clean=listclean, unknown=listunknown,
1257 1261 listsubrepos=True)
1258 1262 for rfiles, sfiles in zip(r, s):
1259 1263 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1260 1264 except error.LookupError:
1261 1265 self.ui.status(_("skipping missing subrepository: %s\n")
1262 1266 % subpath)
1263 1267
1264 1268 for l in r:
1265 1269 l.sort()
1266 1270 return r
1267 1271
1268 1272 def heads(self, start=None):
1269 1273 heads = self.changelog.heads(start)
1270 1274 # sort the output in rev descending order
1271 1275 return sorted(heads, key=self.changelog.rev, reverse=True)
1272 1276
1273 1277 def branchheads(self, branch=None, start=None, closed=False):
1274 1278 '''return a (possibly filtered) list of heads for the given branch
1275 1279
1276 1280 Heads are returned in topological order, from newest to oldest.
1277 1281 If branch is None, use the dirstate branch.
1278 1282 If start is not None, return only heads reachable from start.
1279 1283 If closed is True, return heads that are marked as closed as well.
1280 1284 '''
1281 1285 if branch is None:
1282 1286 branch = self[None].branch()
1283 1287 branches = self.branchmap()
1284 1288 if branch not in branches:
1285 1289 return []
1286 1290 # the cache returns heads ordered lowest to highest
1287 1291 bheads = list(reversed(branches[branch]))
1288 1292 if start is not None:
1289 1293 # filter out the heads that cannot be reached from startrev
1290 1294 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1291 1295 bheads = [h for h in bheads if h in fbheads]
1292 1296 if not closed:
1293 1297 bheads = [h for h in bheads if
1294 1298 ('close' not in self.changelog.read(h)[5])]
1295 1299 return bheads
1296 1300
1297 1301 def branches(self, nodes):
1298 1302 if not nodes:
1299 1303 nodes = [self.changelog.tip()]
1300 1304 b = []
1301 1305 for n in nodes:
1302 1306 t = n
1303 1307 while 1:
1304 1308 p = self.changelog.parents(n)
1305 1309 if p[1] != nullid or p[0] == nullid:
1306 1310 b.append((t, n, p[0], p[1]))
1307 1311 break
1308 1312 n = p[0]
1309 1313 return b
1310 1314
1311 1315 def between(self, pairs):
1312 1316 r = []
1313 1317
1314 1318 for top, bottom in pairs:
1315 1319 n, l, i = top, [], 0
1316 1320 f = 1
1317 1321
1318 1322 while n != bottom and n != nullid:
1319 1323 p = self.changelog.parents(n)[0]
1320 1324 if i == f:
1321 1325 l.append(n)
1322 1326 f = f * 2
1323 1327 n = p
1324 1328 i += 1
1325 1329
1326 1330 r.append(l)
1327 1331
1328 1332 return r
1329 1333
1330 1334 def pull(self, remote, heads=None, force=False):
1331 1335 lock = self.lock()
1332 1336 try:
1333 1337 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1334 1338 force=force)
1335 1339 common, fetch, rheads = tmp
1336 1340 if not fetch:
1337 1341 self.ui.status(_("no changes found\n"))
1338 1342 result = 0
1339 1343 else:
1340 1344 if heads is None and list(common) == [nullid]:
1341 1345 self.ui.status(_("requesting all changes\n"))
1342 1346 elif heads is None and remote.capable('changegroupsubset'):
1343 1347 # issue1320, avoid a race if remote changed after discovery
1344 1348 heads = rheads
1345 1349
1346 1350 if remote.capable('getbundle'):
1347 1351 cg = remote.getbundle('pull', common=common,
1348 1352 heads=heads or rheads)
1349 1353 elif heads is None:
1350 1354 cg = remote.changegroup(fetch, 'pull')
1351 1355 elif not remote.capable('changegroupsubset'):
1352 1356 raise util.Abort(_("partial pull cannot be done because "
1353 1357 "other repository doesn't support "
1354 1358 "changegroupsubset."))
1355 1359 else:
1356 1360 cg = remote.changegroupsubset(fetch, heads, 'pull')
1357 1361 result = self.addchangegroup(cg, 'pull', remote.url(),
1358 1362 lock=lock)
1359 1363 finally:
1360 1364 lock.release()
1361 1365
1362 1366 return result
1363 1367
1364 1368 def checkpush(self, force, revs):
1365 1369 """Extensions can override this function if additional checks have
1366 1370 to be performed before pushing, or call it if they override push
1367 1371 command.
1368 1372 """
1369 1373 pass
1370 1374
1371 1375 def push(self, remote, force=False, revs=None, newbranch=False):
1372 1376 '''Push outgoing changesets (limited by revs) from the current
1373 1377 repository to remote. Return an integer:
1374 1378 - 0 means HTTP error *or* nothing to push
1375 1379 - 1 means we pushed and remote head count is unchanged *or*
1376 1380 we have outgoing changesets but refused to push
1377 1381 - other values as described by addchangegroup()
1378 1382 '''
1379 1383 # there are two ways to push to remote repo:
1380 1384 #
1381 1385 # addchangegroup assumes local user can lock remote
1382 1386 # repo (local filesystem, old ssh servers).
1383 1387 #
1384 1388 # unbundle assumes local user cannot lock remote repo (new ssh
1385 1389 # servers, http servers).
1386 1390
1387 1391 self.checkpush(force, revs)
1388 1392 lock = None
1389 1393 unbundle = remote.capable('unbundle')
1390 1394 if not unbundle:
1391 1395 lock = remote.lock()
1392 1396 try:
1393 1397 cg, remote_heads = discovery.prepush(self, remote, force, revs,
1394 1398 newbranch)
1395 1399 ret = remote_heads
1396 1400 if cg is not None:
1397 1401 if unbundle:
1398 1402 # local repo finds heads on server, finds out what
1399 1403 # revs it must push. once revs transferred, if server
1400 1404 # finds it has different heads (someone else won
1401 1405 # commit/push race), server aborts.
1402 1406 if force:
1403 1407 remote_heads = ['force']
1404 1408 # ssh: return remote's addchangegroup()
1405 1409 # http: return remote's addchangegroup() or 0 for error
1406 1410 ret = remote.unbundle(cg, remote_heads, 'push')
1407 1411 else:
1408 1412 # we return an integer indicating remote head count change
1409 1413 ret = remote.addchangegroup(cg, 'push', self.url(),
1410 1414 lock=lock)
1411 1415 finally:
1412 1416 if lock is not None:
1413 1417 lock.release()
1414 1418
1415 1419 self.ui.debug("checking for updated bookmarks\n")
1416 1420 rb = remote.listkeys('bookmarks')
1417 1421 for k in rb.keys():
1418 1422 if k in self._bookmarks:
1419 1423 nr, nl = rb[k], hex(self._bookmarks[k])
1420 1424 if nr in self:
1421 1425 cr = self[nr]
1422 1426 cl = self[nl]
1423 1427 if cl in cr.descendants():
1424 1428 r = remote.pushkey('bookmarks', k, nr, nl)
1425 1429 if r:
1426 1430 self.ui.status(_("updating bookmark %s\n") % k)
1427 1431 else:
1428 1432 self.ui.warn(_('updating bookmark %s'
1429 1433 ' failed!\n') % k)
1430 1434
1431 1435 return ret
1432 1436
1433 1437 def changegroupinfo(self, nodes, source):
1434 1438 if self.ui.verbose or source == 'bundle':
1435 1439 self.ui.status(_("%d changesets found\n") % len(nodes))
1436 1440 if self.ui.debugflag:
1437 1441 self.ui.debug("list of changesets:\n")
1438 1442 for node in nodes:
1439 1443 self.ui.debug("%s\n" % hex(node))
1440 1444
1441 1445 def changegroupsubset(self, bases, heads, source):
1442 1446 """Compute a changegroup consisting of all the nodes that are
1443 1447 descendents of any of the bases and ancestors of any of the heads.
1444 1448 Return a chunkbuffer object whose read() method will return
1445 1449 successive changegroup chunks.
1446 1450
1447 1451 It is fairly complex as determining which filenodes and which
1448 1452 manifest nodes need to be included for the changeset to be complete
1449 1453 is non-trivial.
1450 1454
1451 1455 Another wrinkle is doing the reverse, figuring out which changeset in
1452 1456 the changegroup a particular filenode or manifestnode belongs to.
1453 1457 """
1454 1458 cl = self.changelog
1455 1459 if not bases:
1456 1460 bases = [nullid]
1457 1461 csets, bases, heads = cl.nodesbetween(bases, heads)
1458 1462 # We assume that all ancestors of bases are known
1459 1463 common = set(cl.ancestors(*[cl.rev(n) for n in bases]))
1460 1464 return self._changegroupsubset(common, csets, heads, source)
1461 1465
1462 1466 def getbundle(self, source, heads=None, common=None):
1463 1467 """Like changegroupsubset, but returns the set difference between the
1464 1468 ancestors of heads and the ancestors common.
1465 1469
1466 1470 If heads is None, use the local heads. If common is None, use [nullid].
1467 1471
1468 1472 The nodes in common might not all be known locally due to the way the
1469 1473 current discovery protocol works.
1470 1474 """
1471 1475 cl = self.changelog
1472 1476 if common:
1473 1477 nm = cl.nodemap
1474 1478 common = [n for n in common if n in nm]
1475 1479 else:
1476 1480 common = [nullid]
1477 1481 if not heads:
1478 1482 heads = cl.heads()
1479 1483 common, missing = cl.findcommonmissing(common, heads)
1480 1484 if not missing:
1481 1485 return None
1482 1486 return self._changegroupsubset(common, missing, heads, source)
1483 1487
1484 1488 def _changegroupsubset(self, commonrevs, csets, heads, source):
1485 1489
1486 1490 cl = self.changelog
1487 1491 mf = self.manifest
1488 1492 mfs = {} # needed manifests
1489 1493 fnodes = {} # needed file nodes
1490 1494 changedfiles = set()
1491 1495 fstate = ['', {}]
1492 1496 count = [0]
1493 1497
1494 1498 # can we go through the fast path ?
1495 1499 heads.sort()
1496 1500 if heads == sorted(self.heads()):
1497 1501 return self._changegroup(csets, source)
1498 1502
1499 1503 # slow path
1500 1504 self.hook('preoutgoing', throw=True, source=source)
1501 1505 self.changegroupinfo(csets, source)
1502 1506
1503 1507 # filter any nodes that claim to be part of the known set
1504 1508 def prune(revlog, missing):
1505 1509 for n in missing:
1506 1510 if revlog.linkrev(revlog.rev(n)) not in commonrevs:
1507 1511 yield n
1508 1512
1509 1513 def lookup(revlog, x):
1510 1514 if revlog == cl:
1511 1515 c = cl.read(x)
1512 1516 changedfiles.update(c[3])
1513 1517 mfs.setdefault(c[0], x)
1514 1518 count[0] += 1
1515 1519 self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
1516 1520 return x
1517 1521 elif revlog == mf:
1518 1522 clnode = mfs[x]
1519 1523 mdata = mf.readfast(x)
1520 1524 for f in changedfiles:
1521 1525 if f in mdata:
1522 1526 fnodes.setdefault(f, {}).setdefault(mdata[f], clnode)
1523 1527 count[0] += 1
1524 1528 self.ui.progress(_('bundling'), count[0],
1525 1529 unit=_('manifests'), total=len(mfs))
1526 1530 return mfs[x]
1527 1531 else:
1528 1532 self.ui.progress(
1529 1533 _('bundling'), count[0], item=fstate[0],
1530 1534 unit=_('files'), total=len(changedfiles))
1531 1535 return fstate[1][x]
1532 1536
1533 1537 bundler = changegroup.bundle10(lookup)
1534 1538
1535 1539 def gengroup():
1536 1540 # Create a changenode group generator that will call our functions
1537 1541 # back to lookup the owning changenode and collect information.
1538 1542 for chunk in cl.group(csets, bundler):
1539 1543 yield chunk
1540 1544 self.ui.progress(_('bundling'), None)
1541 1545
1542 1546 # Create a generator for the manifestnodes that calls our lookup
1543 1547 # and data collection functions back.
1544 1548 count[0] = 0
1545 1549 for chunk in mf.group(prune(mf, mfs), bundler):
1546 1550 yield chunk
1547 1551 self.ui.progress(_('bundling'), None)
1548 1552
1549 1553 mfs.clear()
1550 1554
1551 1555 # Go through all our files in order sorted by name.
1552 1556 count[0] = 0
1553 1557 for fname in sorted(changedfiles):
1554 1558 filerevlog = self.file(fname)
1555 1559 if not len(filerevlog):
1556 1560 raise util.Abort(_("empty or missing revlog for %s") % fname)
1557 1561 fstate[0] = fname
1558 1562 fstate[1] = fnodes.pop(fname, {})
1559 1563 first = True
1560 1564
1561 1565 for chunk in filerevlog.group(prune(filerevlog, fstate[1]),
1562 1566 bundler):
1563 1567 if first:
1564 1568 if chunk == bundler.close():
1565 1569 break
1566 1570 count[0] += 1
1567 1571 yield bundler.fileheader(fname)
1568 1572 first = False
1569 1573 yield chunk
1570 1574 # Signal that no more groups are left.
1571 1575 yield bundler.close()
1572 1576 self.ui.progress(_('bundling'), None)
1573 1577
1574 1578 if csets:
1575 1579 self.hook('outgoing', node=hex(csets[0]), source=source)
1576 1580
1577 1581 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1578 1582
1579 1583 def changegroup(self, basenodes, source):
1580 1584 # to avoid a race we use changegroupsubset() (issue1320)
1581 1585 return self.changegroupsubset(basenodes, self.heads(), source)
1582 1586
1583 1587 def _changegroup(self, nodes, source):
1584 1588 """Compute the changegroup of all nodes that we have that a recipient
1585 1589 doesn't. Return a chunkbuffer object whose read() method will return
1586 1590 successive changegroup chunks.
1587 1591
1588 1592 This is much easier than the previous function as we can assume that
1589 1593 the recipient has any changenode we aren't sending them.
1590 1594
1591 1595 nodes is the set of nodes to send"""
1592 1596
1593 1597 cl = self.changelog
1594 1598 mf = self.manifest
1595 1599 mfs = {}
1596 1600 changedfiles = set()
1597 1601 fstate = ['']
1598 1602 count = [0]
1599 1603
1600 1604 self.hook('preoutgoing', throw=True, source=source)
1601 1605 self.changegroupinfo(nodes, source)
1602 1606
1603 1607 revset = set([cl.rev(n) for n in nodes])
1604 1608
1605 1609 def gennodelst(log):
1606 1610 for r in log:
1607 1611 if log.linkrev(r) in revset:
1608 1612 yield log.node(r)
1609 1613
1610 1614 def lookup(revlog, x):
1611 1615 if revlog == cl:
1612 1616 c = cl.read(x)
1613 1617 changedfiles.update(c[3])
1614 1618 mfs.setdefault(c[0], x)
1615 1619 count[0] += 1
1616 1620 self.ui.progress(_('bundling'), count[0], unit=_('changesets'))
1617 1621 return x
1618 1622 elif revlog == mf:
1619 1623 count[0] += 1
1620 1624 self.ui.progress(_('bundling'), count[0],
1621 1625 unit=_('manifests'), total=len(mfs))
1622 1626 return cl.node(revlog.linkrev(revlog.rev(x)))
1623 1627 else:
1624 1628 self.ui.progress(
1625 1629 _('bundling'), count[0], item=fstate[0],
1626 1630 total=len(changedfiles), unit=_('files'))
1627 1631 return cl.node(revlog.linkrev(revlog.rev(x)))
1628 1632
1629 1633 bundler = changegroup.bundle10(lookup)
1630 1634
1631 1635 def gengroup():
1632 1636 '''yield a sequence of changegroup chunks (strings)'''
1633 1637 # construct a list of all changed files
1634 1638
1635 1639 for chunk in cl.group(nodes, bundler):
1636 1640 yield chunk
1637 1641 self.ui.progress(_('bundling'), None)
1638 1642
1639 1643 count[0] = 0
1640 1644 for chunk in mf.group(gennodelst(mf), bundler):
1641 1645 yield chunk
1642 1646 self.ui.progress(_('bundling'), None)
1643 1647
1644 1648 count[0] = 0
1645 1649 for fname in sorted(changedfiles):
1646 1650 filerevlog = self.file(fname)
1647 1651 if not len(filerevlog):
1648 1652 raise util.Abort(_("empty or missing revlog for %s") % fname)
1649 1653 fstate[0] = fname
1650 1654 first = True
1651 1655 for chunk in filerevlog.group(gennodelst(filerevlog), bundler):
1652 1656 if first:
1653 1657 if chunk == bundler.close():
1654 1658 break
1655 1659 count[0] += 1
1656 1660 yield bundler.fileheader(fname)
1657 1661 first = False
1658 1662 yield chunk
1659 1663 yield bundler.close()
1660 1664 self.ui.progress(_('bundling'), None)
1661 1665
1662 1666 if nodes:
1663 1667 self.hook('outgoing', node=hex(nodes[0]), source=source)
1664 1668
1665 1669 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
1666 1670
1667 1671 def addchangegroup(self, source, srctype, url, emptyok=False, lock=None):
1668 1672 """Add the changegroup returned by source.read() to this repo.
1669 1673 srctype is a string like 'push', 'pull', or 'unbundle'. url is
1670 1674 the URL of the repo where this changegroup is coming from.
1671 1675 If lock is not None, the function takes ownership of the lock
1672 1676 and releases it after the changegroup is added.
1673 1677
1674 1678 Return an integer summarizing the change to this repo:
1675 1679 - nothing changed or no source: 0
1676 1680 - more heads than before: 1+added heads (2..n)
1677 1681 - fewer heads than before: -1-removed heads (-2..-n)
1678 1682 - number of heads stays the same: 1
1679 1683 """
1680 1684 def csmap(x):
1681 1685 self.ui.debug("add changeset %s\n" % short(x))
1682 1686 return len(cl)
1683 1687
1684 1688 def revmap(x):
1685 1689 return cl.rev(x)
1686 1690
1687 1691 if not source:
1688 1692 return 0
1689 1693
1690 1694 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1691 1695
1692 1696 changesets = files = revisions = 0
1693 1697 efiles = set()
1694 1698
1695 1699 # write changelog data to temp files so concurrent readers will not see
1696 1700 # inconsistent view
1697 1701 cl = self.changelog
1698 1702 cl.delayupdate()
1699 1703 oldheads = cl.heads()
1700 1704
1701 1705 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
1702 1706 try:
1703 1707 trp = weakref.proxy(tr)
1704 1708 # pull off the changeset group
1705 1709 self.ui.status(_("adding changesets\n"))
1706 1710 clstart = len(cl)
1707 1711 class prog(object):
1708 1712 step = _('changesets')
1709 1713 count = 1
1710 1714 ui = self.ui
1711 1715 total = None
1712 1716 def __call__(self):
1713 1717 self.ui.progress(self.step, self.count, unit=_('chunks'),
1714 1718 total=self.total)
1715 1719 self.count += 1
1716 1720 pr = prog()
1717 1721 source.callback = pr
1718 1722
1719 1723 source.changelogheader()
1720 1724 if (cl.addgroup(source, csmap, trp) is None
1721 1725 and not emptyok):
1722 1726 raise util.Abort(_("received changelog group is empty"))
1723 1727 clend = len(cl)
1724 1728 changesets = clend - clstart
1725 1729 for c in xrange(clstart, clend):
1726 1730 efiles.update(self[c].files())
1727 1731 efiles = len(efiles)
1728 1732 self.ui.progress(_('changesets'), None)
1729 1733
1730 1734 # pull off the manifest group
1731 1735 self.ui.status(_("adding manifests\n"))
1732 1736 pr.step = _('manifests')
1733 1737 pr.count = 1
1734 1738 pr.total = changesets # manifests <= changesets
1735 1739 # no need to check for empty manifest group here:
1736 1740 # if the result of the merge of 1 and 2 is the same in 3 and 4,
1737 1741 # no new manifest will be created and the manifest group will
1738 1742 # be empty during the pull
1739 1743 source.manifestheader()
1740 1744 self.manifest.addgroup(source, revmap, trp)
1741 1745 self.ui.progress(_('manifests'), None)
1742 1746
1743 1747 needfiles = {}
1744 1748 if self.ui.configbool('server', 'validate', default=False):
1745 1749 # validate incoming csets have their manifests
1746 1750 for cset in xrange(clstart, clend):
1747 1751 mfest = self.changelog.read(self.changelog.node(cset))[0]
1748 1752 mfest = self.manifest.readdelta(mfest)
1749 1753 # store file nodes we must see
1750 1754 for f, n in mfest.iteritems():
1751 1755 needfiles.setdefault(f, set()).add(n)
1752 1756
1753 1757 # process the files
1754 1758 self.ui.status(_("adding file changes\n"))
1755 1759 pr.step = 'files'
1756 1760 pr.count = 1
1757 1761 pr.total = efiles
1758 1762 source.callback = None
1759 1763
1760 1764 while 1:
1761 1765 chunkdata = source.filelogheader()
1762 1766 if not chunkdata:
1763 1767 break
1764 1768 f = chunkdata["filename"]
1765 1769 self.ui.debug("adding %s revisions\n" % f)
1766 1770 pr()
1767 1771 fl = self.file(f)
1768 1772 o = len(fl)
1769 1773 if fl.addgroup(source, revmap, trp) is None:
1770 1774 raise util.Abort(_("received file revlog group is empty"))
1771 1775 revisions += len(fl) - o
1772 1776 files += 1
1773 1777 if f in needfiles:
1774 1778 needs = needfiles[f]
1775 1779 for new in xrange(o, len(fl)):
1776 1780 n = fl.node(new)
1777 1781 if n in needs:
1778 1782 needs.remove(n)
1779 1783 if not needs:
1780 1784 del needfiles[f]
1781 1785 self.ui.progress(_('files'), None)
1782 1786
1783 1787 for f, needs in needfiles.iteritems():
1784 1788 fl = self.file(f)
1785 1789 for n in needs:
1786 1790 try:
1787 1791 fl.rev(n)
1788 1792 except error.LookupError:
1789 1793 raise util.Abort(
1790 1794 _('missing file data for %s:%s - run hg verify') %
1791 1795 (f, hex(n)))
1792 1796
1793 1797 dh = 0
1794 1798 if oldheads:
1795 1799 heads = cl.heads()
1796 1800 dh = len(heads) - len(oldheads)
1797 1801 for h in heads:
1798 1802 if h not in oldheads and 'close' in self[h].extra():
1799 1803 dh -= 1
1800 1804 htext = ""
1801 1805 if dh:
1802 1806 htext = _(" (%+d heads)") % dh
1803 1807
1804 1808 self.ui.status(_("added %d changesets"
1805 1809 " with %d changes to %d files%s\n")
1806 1810 % (changesets, revisions, files, htext))
1807 1811
1808 1812 if changesets > 0:
1809 1813 p = lambda: cl.writepending() and self.root or ""
1810 1814 self.hook('pretxnchangegroup', throw=True,
1811 1815 node=hex(cl.node(clstart)), source=srctype,
1812 1816 url=url, pending=p)
1813 1817
1814 1818 # make changelog see real files again
1815 1819 cl.finalize(trp)
1816 1820
1817 1821 tr.close()
1818 1822 finally:
1819 1823 tr.release()
1820 1824 if lock:
1821 1825 lock.release()
1822 1826
1823 1827 if changesets > 0:
1824 1828 # forcefully update the on-disk branch cache
1825 1829 self.ui.debug("updating the branch cache\n")
1826 1830 self.updatebranchcache()
1827 1831 self.hook("changegroup", node=hex(cl.node(clstart)),
1828 1832 source=srctype, url=url)
1829 1833
1830 1834 for i in xrange(clstart, clend):
1831 1835 self.hook("incoming", node=hex(cl.node(i)),
1832 1836 source=srctype, url=url)
1833 1837
1834 1838 # never return 0 here:
1835 1839 if dh < 0:
1836 1840 return dh - 1
1837 1841 else:
1838 1842 return dh + 1
1839 1843
1840 1844 def stream_in(self, remote, requirements):
1841 1845 lock = self.lock()
1842 1846 try:
1843 1847 fp = remote.stream_out()
1844 1848 l = fp.readline()
1845 1849 try:
1846 1850 resp = int(l)
1847 1851 except ValueError:
1848 1852 raise error.ResponseError(
1849 1853 _('Unexpected response from remote server:'), l)
1850 1854 if resp == 1:
1851 1855 raise util.Abort(_('operation forbidden by server'))
1852 1856 elif resp == 2:
1853 1857 raise util.Abort(_('locking the remote repository failed'))
1854 1858 elif resp != 0:
1855 1859 raise util.Abort(_('the server sent an unknown error code'))
1856 1860 self.ui.status(_('streaming all changes\n'))
1857 1861 l = fp.readline()
1858 1862 try:
1859 1863 total_files, total_bytes = map(int, l.split(' ', 1))
1860 1864 except (ValueError, TypeError):
1861 1865 raise error.ResponseError(
1862 1866 _('Unexpected response from remote server:'), l)
1863 1867 self.ui.status(_('%d files to transfer, %s of data\n') %
1864 1868 (total_files, util.bytecount(total_bytes)))
1865 1869 start = time.time()
1866 1870 for i in xrange(total_files):
1867 1871 # XXX doesn't support '\n' or '\r' in filenames
1868 1872 l = fp.readline()
1869 1873 try:
1870 1874 name, size = l.split('\0', 1)
1871 1875 size = int(size)
1872 1876 except (ValueError, TypeError):
1873 1877 raise error.ResponseError(
1874 1878 _('Unexpected response from remote server:'), l)
1875 1879 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
1876 1880 # for backwards compat, name was partially encoded
1877 1881 ofp = self.sopener(store.decodedir(name), 'w')
1878 1882 for chunk in util.filechunkiter(fp, limit=size):
1879 1883 ofp.write(chunk)
1880 1884 ofp.close()
1881 1885 elapsed = time.time() - start
1882 1886 if elapsed <= 0:
1883 1887 elapsed = 0.001
1884 1888 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1885 1889 (util.bytecount(total_bytes), elapsed,
1886 1890 util.bytecount(total_bytes / elapsed)))
1887 1891
1888 1892 # new requirements = old non-format requirements + new format-related
1889 1893 # requirements from the streamed-in repository
1890 1894 requirements.update(set(self.requirements) - self.supportedformats)
1891 1895 self._applyrequirements(requirements)
1892 1896 self._writerequirements()
1893 1897
1894 1898 self.invalidate()
1895 1899 return len(self.heads()) + 1
1896 1900 finally:
1897 1901 lock.release()
1898 1902
1899 1903 def clone(self, remote, heads=[], stream=False):
1900 1904 '''clone remote repository.
1901 1905
1902 1906 keyword arguments:
1903 1907 heads: list of revs to clone (forces use of pull)
1904 1908 stream: use streaming clone if possible'''
1905 1909
1906 1910 # now, all clients that can request uncompressed clones can
1907 1911 # read repo formats supported by all servers that can serve
1908 1912 # them.
1909 1913
1910 1914 # if revlog format changes, client will have to check version
1911 1915 # and format flags on "stream" capability, and use
1912 1916 # uncompressed only if compatible.
1913 1917
1914 1918 if stream and not heads:
1915 1919 # 'stream' means remote revlog format is revlogv1 only
1916 1920 if remote.capable('stream'):
1917 1921 return self.stream_in(remote, set(('revlogv1',)))
1918 1922 # otherwise, 'streamreqs' contains the remote revlog format
1919 1923 streamreqs = remote.capable('streamreqs')
1920 1924 if streamreqs:
1921 1925 streamreqs = set(streamreqs.split(','))
1922 1926 # if we support it, stream in and adjust our requirements
1923 1927 if not streamreqs - self.supportedformats:
1924 1928 return self.stream_in(remote, streamreqs)
1925 1929 return self.pull(remote, heads)
1926 1930
1927 1931 def pushkey(self, namespace, key, old, new):
1928 1932 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1929 1933 old=old, new=new)
1930 1934 ret = pushkey.push(self, namespace, key, old, new)
1931 1935 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1932 1936 ret=ret)
1933 1937 return ret
1934 1938
1935 1939 def listkeys(self, namespace):
1936 1940 self.hook('prelistkeys', throw=True, namespace=namespace)
1937 1941 values = pushkey.list(self, namespace)
1938 1942 self.hook('listkeys', namespace=namespace, values=values)
1939 1943 return values
1940 1944
1941 1945 def debugwireargs(self, one, two, three=None, four=None, five=None):
1942 1946 '''used to test argument passing over the wire'''
1943 1947 return "%s %s %s %s %s" % (one, two, three, four, five)
1944 1948
1945 1949 # used to avoid circular references so destructors work
1946 1950 def aftertrans(files):
1947 1951 renamefiles = [tuple(t) for t in files]
1948 1952 def a():
1949 1953 for src, dest in renamefiles:
1950 1954 util.rename(src, dest)
1951 1955 return a
1952 1956
1953 1957 def instance(ui, path, create):
1954 1958 return localrepository(ui, util.localpath(path), create)
1955 1959
1956 1960 def islocal(path):
1957 1961 return True
@@ -1,1249 +1,1259
1 1 # revlog.py - storage back-end for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Storage back-end for Mercurial.
9 9
10 10 This provides efficient delta storage with O(1) retrieve and append
11 11 and O(changes) merge between branches.
12 12 """
13 13
14 14 # import stuff from node for others to import from revlog
15 15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
16 16 from i18n import _
17 17 import ancestor, mdiff, parsers, error, util
18 18 import struct, zlib, errno
19 19
20 20 _pack = struct.pack
21 21 _unpack = struct.unpack
22 22 _compress = zlib.compress
23 23 _decompress = zlib.decompress
24 24 _sha = util.sha1
25 25
26 26 # revlog header flags
27 27 REVLOGV0 = 0
28 28 REVLOGNG = 1
29 29 REVLOGNGINLINEDATA = (1 << 16)
30 30 REVLOGGENERALDELTA = (1 << 17)
31 31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
32 32 REVLOG_DEFAULT_FORMAT = REVLOGNG
33 33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
34 34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
35 35
36 36 # revlog index flags
37 37 REVIDX_KNOWN_FLAGS = 0
38 38
39 39 # max size of revlog with inline data
40 40 _maxinline = 131072
41 41 _chunksize = 1048576
42 42
43 43 RevlogError = error.RevlogError
44 44 LookupError = error.LookupError
45 45
46 46 def getoffset(q):
47 47 return int(q >> 16)
48 48
49 49 def gettype(q):
50 50 return int(q & 0xFFFF)
51 51
52 52 def offset_type(offset, type):
53 53 return long(long(offset) << 16 | type)
54 54
55 55 nullhash = _sha(nullid)
56 56
57 57 def hash(text, p1, p2):
58 58 """generate a hash from the given text and its parent hashes
59 59
60 60 This hash combines both the current file contents and its history
61 61 in a manner that makes it easy to distinguish nodes with the same
62 62 content in the revision graph.
63 63 """
64 64 # As of now, if one of the parent node is null, p2 is null
65 65 if p2 == nullid:
66 66 # deep copy of a hash is faster than creating one
67 67 s = nullhash.copy()
68 68 s.update(p1)
69 69 else:
70 70 # none of the parent nodes are nullid
71 71 l = [p1, p2]
72 72 l.sort()
73 73 s = _sha(l[0])
74 74 s.update(l[1])
75 75 s.update(text)
76 76 return s.digest()
77 77
78 78 def compress(text):
79 79 """ generate a possibly-compressed representation of text """
80 80 if not text:
81 81 return ("", text)
82 82 l = len(text)
83 83 bin = None
84 84 if l < 44:
85 85 pass
86 86 elif l > 1000000:
87 87 # zlib makes an internal copy, thus doubling memory usage for
88 88 # large files, so lets do this in pieces
89 89 z = zlib.compressobj()
90 90 p = []
91 91 pos = 0
92 92 while pos < l:
93 93 pos2 = pos + 2**20
94 94 p.append(z.compress(text[pos:pos2]))
95 95 pos = pos2
96 96 p.append(z.flush())
97 97 if sum(map(len, p)) < l:
98 98 bin = "".join(p)
99 99 else:
100 100 bin = _compress(text)
101 101 if bin is None or len(bin) > l:
102 102 if text[0] == '\0':
103 103 return ("", text)
104 104 return ('u', text)
105 105 return ("", bin)
106 106
107 107 def decompress(bin):
108 108 """ decompress the given input """
109 109 if not bin:
110 110 return bin
111 111 t = bin[0]
112 112 if t == '\0':
113 113 return bin
114 114 if t == 'x':
115 115 return _decompress(bin)
116 116 if t == 'u':
117 117 return bin[1:]
118 118 raise RevlogError(_("unknown compression type %r") % t)
119 119
120 120 indexformatv0 = ">4l20s20s20s"
121 121 v0shaoffset = 56
122 122
123 123 class revlogoldio(object):
124 124 def __init__(self):
125 125 self.size = struct.calcsize(indexformatv0)
126 126
127 127 def parseindex(self, data, inline):
128 128 s = self.size
129 129 index = []
130 130 nodemap = {nullid: nullrev}
131 131 n = off = 0
132 132 l = len(data)
133 133 while off + s <= l:
134 134 cur = data[off:off + s]
135 135 off += s
136 136 e = _unpack(indexformatv0, cur)
137 137 # transform to revlogv1 format
138 138 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
139 139 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
140 140 index.append(e2)
141 141 nodemap[e[6]] = n
142 142 n += 1
143 143
144 144 # add the magic null revision at -1
145 145 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
146 146
147 147 return index, nodemap, None
148 148
149 149 def packentry(self, entry, node, version, rev):
150 150 if gettype(entry[0]):
151 151 raise RevlogError(_("index entry flags need RevlogNG"))
152 152 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
153 153 node(entry[5]), node(entry[6]), entry[7])
154 154 return _pack(indexformatv0, *e2)
155 155
156 156 # index ng:
157 157 # 6 bytes: offset
158 158 # 2 bytes: flags
159 159 # 4 bytes: compressed length
160 160 # 4 bytes: uncompressed length
161 161 # 4 bytes: base rev
162 162 # 4 bytes: link rev
163 163 # 4 bytes: parent 1 rev
164 164 # 4 bytes: parent 2 rev
165 165 # 32 bytes: nodeid
166 166 indexformatng = ">Qiiiiii20s12x"
167 167 ngshaoffset = 32
168 168 versionformat = ">I"
169 169
170 170 class revlogio(object):
171 171 def __init__(self):
172 172 self.size = struct.calcsize(indexformatng)
173 173
174 174 def parseindex(self, data, inline):
175 175 # call the C implementation to parse the index data
176 176 index, cache = parsers.parse_index2(data, inline)
177 177 return index, None, cache
178 178
179 179 def packentry(self, entry, node, version, rev):
180 180 p = _pack(indexformatng, *entry)
181 181 if rev == 0:
182 182 p = _pack(versionformat, version) + p[4:]
183 183 return p
184 184
185 185 class revlog(object):
186 186 """
187 187 the underlying revision storage object
188 188
189 189 A revlog consists of two parts, an index and the revision data.
190 190
191 191 The index is a file with a fixed record size containing
192 192 information on each revision, including its nodeid (hash), the
193 193 nodeids of its parents, the position and offset of its data within
194 194 the data file, and the revision it's based on. Finally, each entry
195 195 contains a linkrev entry that can serve as a pointer to external
196 196 data.
197 197
198 198 The revision data itself is a linear collection of data chunks.
199 199 Each chunk represents a revision and is usually represented as a
200 200 delta against the previous chunk. To bound lookup time, runs of
201 201 deltas are limited to about 2 times the length of the original
202 202 version data. This makes retrieval of a version proportional to
203 203 its size, or O(1) relative to the number of revisions.
204 204
205 205 Both pieces of the revlog are written to in an append-only
206 206 fashion, which means we never need to rewrite a file to insert or
207 207 remove data, and can use some simple techniques to avoid the need
208 208 for locking while reading.
209 209 """
210 210 def __init__(self, opener, indexfile):
211 211 """
212 212 create a revlog object
213 213
214 214 opener is a function that abstracts the file opening operation
215 215 and can be used to implement COW semantics or the like.
216 216 """
217 217 self.indexfile = indexfile
218 218 self.datafile = indexfile[:-2] + ".d"
219 219 self.opener = opener
220 220 self._cache = None
221 221 self._basecache = None
222 222 self._chunkcache = (0, '')
223 223 self.index = []
224 224 self._pcache = {}
225 225 self._nodecache = {nullid: nullrev}
226 226 self._nodepos = None
227 227
228 228 v = REVLOG_DEFAULT_VERSION
229 if hasattr(opener, 'options') and 'defversion' in opener.options:
230 v = opener.options['defversion']
231 if v & REVLOGNG:
232 v |= REVLOGNGINLINEDATA
229 if hasattr(opener, 'options'):
230 if 'defversion' in opener.options:
231 v = opener.options['defversion']
232 if v & REVLOGNG:
233 v |= REVLOGNGINLINEDATA
234 if v & REVLOGNG and 'generaldelta' in opener.options:
235 v |= REVLOGGENERALDELTA
233 236
234 237 i = ''
235 238 try:
236 239 f = self.opener(self.indexfile)
237 240 i = f.read()
238 241 f.close()
239 242 if len(i) > 0:
240 243 v = struct.unpack(versionformat, i[:4])[0]
241 244 except IOError, inst:
242 245 if inst.errno != errno.ENOENT:
243 246 raise
244 247
245 248 self.version = v
246 249 self._inline = v & REVLOGNGINLINEDATA
247 250 self._generaldelta = v & REVLOGGENERALDELTA
248 251 flags = v & ~0xFFFF
249 252 fmt = v & 0xFFFF
250 253 if fmt == REVLOGV0 and flags:
251 254 raise RevlogError(_("index %s unknown flags %#04x for format v0")
252 255 % (self.indexfile, flags >> 16))
253 256 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
254 257 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
255 258 % (self.indexfile, flags >> 16))
256 259 elif fmt > REVLOGNG:
257 260 raise RevlogError(_("index %s unknown format %d")
258 261 % (self.indexfile, fmt))
259 262
260 263 self._io = revlogio()
261 264 if self.version == REVLOGV0:
262 265 self._io = revlogoldio()
263 266 try:
264 267 d = self._io.parseindex(i, self._inline)
265 268 except (ValueError, IndexError):
266 269 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
267 270 self.index, nodemap, self._chunkcache = d
268 271 if nodemap is not None:
269 272 self.nodemap = self._nodecache = nodemap
270 273 if not self._chunkcache:
271 274 self._chunkclear()
272 275
273 276 def tip(self):
274 277 return self.node(len(self.index) - 2)
275 278 def __len__(self):
276 279 return len(self.index) - 1
277 280 def __iter__(self):
278 281 for i in xrange(len(self)):
279 282 yield i
280 283
281 284 @util.propertycache
282 285 def nodemap(self):
283 286 self.rev(self.node(0))
284 287 return self._nodecache
285 288
286 289 def rev(self, node):
287 290 try:
288 291 return self._nodecache[node]
289 292 except KeyError:
290 293 n = self._nodecache
291 294 i = self.index
292 295 p = self._nodepos
293 296 if p is None:
294 297 p = len(i) - 2
295 298 for r in xrange(p, -1, -1):
296 299 v = i[r][7]
297 300 n[v] = r
298 301 if v == node:
299 302 self._nodepos = r - 1
300 303 return r
301 304 raise LookupError(node, self.indexfile, _('no node'))
302 305
303 306 def node(self, rev):
304 307 return self.index[rev][7]
305 308 def linkrev(self, rev):
306 309 return self.index[rev][4]
307 310 def parents(self, node):
308 311 i = self.index
309 312 d = i[self.rev(node)]
310 313 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
311 314 def parentrevs(self, rev):
312 315 return self.index[rev][5:7]
313 316 def start(self, rev):
314 317 return int(self.index[rev][0] >> 16)
315 318 def end(self, rev):
316 319 return self.start(rev) + self.length(rev)
317 320 def length(self, rev):
318 321 return self.index[rev][1]
319 322 def chainbase(self, rev):
320 323 index = self.index
321 324 base = index[rev][3]
322 325 while base != rev:
323 326 rev = base
324 327 base = index[rev][3]
325 328 return base
326 329 def flags(self, rev):
327 330 return self.index[rev][0] & 0xFFFF
328 331 def rawsize(self, rev):
329 332 """return the length of the uncompressed text for a given revision"""
330 333 l = self.index[rev][2]
331 334 if l >= 0:
332 335 return l
333 336
334 337 t = self.revision(self.node(rev))
335 338 return len(t)
336 339 size = rawsize
337 340
338 341 def reachable(self, node, stop=None):
339 342 """return the set of all nodes ancestral to a given node, including
340 343 the node itself, stopping when stop is matched"""
341 344 reachable = set((node,))
342 345 visit = [node]
343 346 if stop:
344 347 stopn = self.rev(stop)
345 348 else:
346 349 stopn = 0
347 350 while visit:
348 351 n = visit.pop(0)
349 352 if n == stop:
350 353 continue
351 354 if n == nullid:
352 355 continue
353 356 for p in self.parents(n):
354 357 if self.rev(p) < stopn:
355 358 continue
356 359 if p not in reachable:
357 360 reachable.add(p)
358 361 visit.append(p)
359 362 return reachable
360 363
361 364 def ancestors(self, *revs):
362 365 """Generate the ancestors of 'revs' in reverse topological order.
363 366
364 367 Yield a sequence of revision numbers starting with the parents
365 368 of each revision in revs, i.e., each revision is *not* considered
366 369 an ancestor of itself. Results are in breadth-first order:
367 370 parents of each rev in revs, then parents of those, etc. Result
368 371 does not include the null revision."""
369 372 visit = list(revs)
370 373 seen = set([nullrev])
371 374 while visit:
372 375 for parent in self.parentrevs(visit.pop(0)):
373 376 if parent not in seen:
374 377 visit.append(parent)
375 378 seen.add(parent)
376 379 yield parent
377 380
378 381 def descendants(self, *revs):
379 382 """Generate the descendants of 'revs' in revision order.
380 383
381 384 Yield a sequence of revision numbers starting with a child of
382 385 some rev in revs, i.e., each revision is *not* considered a
383 386 descendant of itself. Results are ordered by revision number (a
384 387 topological sort)."""
385 388 first = min(revs)
386 389 if first == nullrev:
387 390 for i in self:
388 391 yield i
389 392 return
390 393
391 394 seen = set(revs)
392 395 for i in xrange(first + 1, len(self)):
393 396 for x in self.parentrevs(i):
394 397 if x != nullrev and x in seen:
395 398 seen.add(i)
396 399 yield i
397 400 break
398 401
399 402 def findcommonmissing(self, common=None, heads=None):
400 403 """Return a tuple of the ancestors of common and the ancestors of heads
401 404 that are not ancestors of common.
402 405
403 406 More specifically, the second element is a list of nodes N such that
404 407 every N satisfies the following constraints:
405 408
406 409 1. N is an ancestor of some node in 'heads'
407 410 2. N is not an ancestor of any node in 'common'
408 411
409 412 The list is sorted by revision number, meaning it is
410 413 topologically sorted.
411 414
412 415 'heads' and 'common' are both lists of node IDs. If heads is
413 416 not supplied, uses all of the revlog's heads. If common is not
414 417 supplied, uses nullid."""
415 418 if common is None:
416 419 common = [nullid]
417 420 if heads is None:
418 421 heads = self.heads()
419 422
420 423 common = [self.rev(n) for n in common]
421 424 heads = [self.rev(n) for n in heads]
422 425
423 426 # we want the ancestors, but inclusive
424 427 has = set(self.ancestors(*common))
425 428 has.add(nullrev)
426 429 has.update(common)
427 430
428 431 # take all ancestors from heads that aren't in has
429 432 missing = set()
430 433 visit = [r for r in heads if r not in has]
431 434 while visit:
432 435 r = visit.pop(0)
433 436 if r in missing:
434 437 continue
435 438 else:
436 439 missing.add(r)
437 440 for p in self.parentrevs(r):
438 441 if p not in has:
439 442 visit.append(p)
440 443 missing = list(missing)
441 444 missing.sort()
442 445 return has, [self.node(r) for r in missing]
443 446
444 447 def findmissing(self, common=None, heads=None):
445 448 """Return the ancestors of heads that are not ancestors of common.
446 449
447 450 More specifically, return a list of nodes N such that every N
448 451 satisfies the following constraints:
449 452
450 453 1. N is an ancestor of some node in 'heads'
451 454 2. N is not an ancestor of any node in 'common'
452 455
453 456 The list is sorted by revision number, meaning it is
454 457 topologically sorted.
455 458
456 459 'heads' and 'common' are both lists of node IDs. If heads is
457 460 not supplied, uses all of the revlog's heads. If common is not
458 461 supplied, uses nullid."""
459 462 _common, missing = self.findcommonmissing(common, heads)
460 463 return missing
461 464
462 465 def nodesbetween(self, roots=None, heads=None):
463 466 """Return a topological path from 'roots' to 'heads'.
464 467
465 468 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
466 469 topologically sorted list of all nodes N that satisfy both of
467 470 these constraints:
468 471
469 472 1. N is a descendant of some node in 'roots'
470 473 2. N is an ancestor of some node in 'heads'
471 474
472 475 Every node is considered to be both a descendant and an ancestor
473 476 of itself, so every reachable node in 'roots' and 'heads' will be
474 477 included in 'nodes'.
475 478
476 479 'outroots' is the list of reachable nodes in 'roots', i.e., the
477 480 subset of 'roots' that is returned in 'nodes'. Likewise,
478 481 'outheads' is the subset of 'heads' that is also in 'nodes'.
479 482
480 483 'roots' and 'heads' are both lists of node IDs. If 'roots' is
481 484 unspecified, uses nullid as the only root. If 'heads' is
482 485 unspecified, uses list of all of the revlog's heads."""
483 486 nonodes = ([], [], [])
484 487 if roots is not None:
485 488 roots = list(roots)
486 489 if not roots:
487 490 return nonodes
488 491 lowestrev = min([self.rev(n) for n in roots])
489 492 else:
490 493 roots = [nullid] # Everybody's a descendent of nullid
491 494 lowestrev = nullrev
492 495 if (lowestrev == nullrev) and (heads is None):
493 496 # We want _all_ the nodes!
494 497 return ([self.node(r) for r in self], [nullid], list(self.heads()))
495 498 if heads is None:
496 499 # All nodes are ancestors, so the latest ancestor is the last
497 500 # node.
498 501 highestrev = len(self) - 1
499 502 # Set ancestors to None to signal that every node is an ancestor.
500 503 ancestors = None
501 504 # Set heads to an empty dictionary for later discovery of heads
502 505 heads = {}
503 506 else:
504 507 heads = list(heads)
505 508 if not heads:
506 509 return nonodes
507 510 ancestors = set()
508 511 # Turn heads into a dictionary so we can remove 'fake' heads.
509 512 # Also, later we will be using it to filter out the heads we can't
510 513 # find from roots.
511 514 heads = dict.fromkeys(heads, False)
512 515 # Start at the top and keep marking parents until we're done.
513 516 nodestotag = set(heads)
514 517 # Remember where the top was so we can use it as a limit later.
515 518 highestrev = max([self.rev(n) for n in nodestotag])
516 519 while nodestotag:
517 520 # grab a node to tag
518 521 n = nodestotag.pop()
519 522 # Never tag nullid
520 523 if n == nullid:
521 524 continue
522 525 # A node's revision number represents its place in a
523 526 # topologically sorted list of nodes.
524 527 r = self.rev(n)
525 528 if r >= lowestrev:
526 529 if n not in ancestors:
527 530 # If we are possibly a descendent of one of the roots
528 531 # and we haven't already been marked as an ancestor
529 532 ancestors.add(n) # Mark as ancestor
530 533 # Add non-nullid parents to list of nodes to tag.
531 534 nodestotag.update([p for p in self.parents(n) if
532 535 p != nullid])
533 536 elif n in heads: # We've seen it before, is it a fake head?
534 537 # So it is, real heads should not be the ancestors of
535 538 # any other heads.
536 539 heads.pop(n)
537 540 if not ancestors:
538 541 return nonodes
539 542 # Now that we have our set of ancestors, we want to remove any
540 543 # roots that are not ancestors.
541 544
542 545 # If one of the roots was nullid, everything is included anyway.
543 546 if lowestrev > nullrev:
544 547 # But, since we weren't, let's recompute the lowest rev to not
545 548 # include roots that aren't ancestors.
546 549
547 550 # Filter out roots that aren't ancestors of heads
548 551 roots = [n for n in roots if n in ancestors]
549 552 # Recompute the lowest revision
550 553 if roots:
551 554 lowestrev = min([self.rev(n) for n in roots])
552 555 else:
553 556 # No more roots? Return empty list
554 557 return nonodes
555 558 else:
556 559 # We are descending from nullid, and don't need to care about
557 560 # any other roots.
558 561 lowestrev = nullrev
559 562 roots = [nullid]
560 563 # Transform our roots list into a set.
561 564 descendents = set(roots)
562 565 # Also, keep the original roots so we can filter out roots that aren't
563 566 # 'real' roots (i.e. are descended from other roots).
564 567 roots = descendents.copy()
565 568 # Our topologically sorted list of output nodes.
566 569 orderedout = []
567 570 # Don't start at nullid since we don't want nullid in our output list,
568 571 # and if nullid shows up in descedents, empty parents will look like
569 572 # they're descendents.
570 573 for r in xrange(max(lowestrev, 0), highestrev + 1):
571 574 n = self.node(r)
572 575 isdescendent = False
573 576 if lowestrev == nullrev: # Everybody is a descendent of nullid
574 577 isdescendent = True
575 578 elif n in descendents:
576 579 # n is already a descendent
577 580 isdescendent = True
578 581 # This check only needs to be done here because all the roots
579 582 # will start being marked is descendents before the loop.
580 583 if n in roots:
581 584 # If n was a root, check if it's a 'real' root.
582 585 p = tuple(self.parents(n))
583 586 # If any of its parents are descendents, it's not a root.
584 587 if (p[0] in descendents) or (p[1] in descendents):
585 588 roots.remove(n)
586 589 else:
587 590 p = tuple(self.parents(n))
588 591 # A node is a descendent if either of its parents are
589 592 # descendents. (We seeded the dependents list with the roots
590 593 # up there, remember?)
591 594 if (p[0] in descendents) or (p[1] in descendents):
592 595 descendents.add(n)
593 596 isdescendent = True
594 597 if isdescendent and ((ancestors is None) or (n in ancestors)):
595 598 # Only include nodes that are both descendents and ancestors.
596 599 orderedout.append(n)
597 600 if (ancestors is not None) and (n in heads):
598 601 # We're trying to figure out which heads are reachable
599 602 # from roots.
600 603 # Mark this head as having been reached
601 604 heads[n] = True
602 605 elif ancestors is None:
603 606 # Otherwise, we're trying to discover the heads.
604 607 # Assume this is a head because if it isn't, the next step
605 608 # will eventually remove it.
606 609 heads[n] = True
607 610 # But, obviously its parents aren't.
608 611 for p in self.parents(n):
609 612 heads.pop(p, None)
610 613 heads = [n for n, flag in heads.iteritems() if flag]
611 614 roots = list(roots)
612 615 assert orderedout
613 616 assert roots
614 617 assert heads
615 618 return (orderedout, roots, heads)
616 619
617 620 def headrevs(self):
618 621 count = len(self)
619 622 if not count:
620 623 return [nullrev]
621 624 ishead = [1] * (count + 1)
622 625 index = self.index
623 626 for r in xrange(count):
624 627 e = index[r]
625 628 ishead[e[5]] = ishead[e[6]] = 0
626 629 return [r for r in xrange(count) if ishead[r]]
627 630
628 631 def heads(self, start=None, stop=None):
629 632 """return the list of all nodes that have no children
630 633
631 634 if start is specified, only heads that are descendants of
632 635 start will be returned
633 636 if stop is specified, it will consider all the revs from stop
634 637 as if they had no children
635 638 """
636 639 if start is None and stop is None:
637 640 if not len(self):
638 641 return [nullid]
639 642 return [self.node(r) for r in self.headrevs()]
640 643
641 644 if start is None:
642 645 start = nullid
643 646 if stop is None:
644 647 stop = []
645 648 stoprevs = set([self.rev(n) for n in stop])
646 649 startrev = self.rev(start)
647 650 reachable = set((startrev,))
648 651 heads = set((startrev,))
649 652
650 653 parentrevs = self.parentrevs
651 654 for r in xrange(startrev + 1, len(self)):
652 655 for p in parentrevs(r):
653 656 if p in reachable:
654 657 if r not in stoprevs:
655 658 reachable.add(r)
656 659 heads.add(r)
657 660 if p in heads and p not in stoprevs:
658 661 heads.remove(p)
659 662
660 663 return [self.node(r) for r in heads]
661 664
662 665 def children(self, node):
663 666 """find the children of a given node"""
664 667 c = []
665 668 p = self.rev(node)
666 669 for r in range(p + 1, len(self)):
667 670 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
668 671 if prevs:
669 672 for pr in prevs:
670 673 if pr == p:
671 674 c.append(self.node(r))
672 675 elif p == nullrev:
673 676 c.append(self.node(r))
674 677 return c
675 678
676 679 def descendant(self, start, end):
677 680 if start == nullrev:
678 681 return True
679 682 for i in self.descendants(start):
680 683 if i == end:
681 684 return True
682 685 elif i > end:
683 686 break
684 687 return False
685 688
686 689 def ancestor(self, a, b):
687 690 """calculate the least common ancestor of nodes a and b"""
688 691
689 692 # fast path, check if it is a descendant
690 693 a, b = self.rev(a), self.rev(b)
691 694 start, end = sorted((a, b))
692 695 if self.descendant(start, end):
693 696 return self.node(start)
694 697
695 698 def parents(rev):
696 699 return [p for p in self.parentrevs(rev) if p != nullrev]
697 700
698 701 c = ancestor.ancestor(a, b, parents)
699 702 if c is None:
700 703 return nullid
701 704
702 705 return self.node(c)
703 706
704 707 def _match(self, id):
705 708 if isinstance(id, (long, int)):
706 709 # rev
707 710 return self.node(id)
708 711 if len(id) == 20:
709 712 # possibly a binary node
710 713 # odds of a binary node being all hex in ASCII are 1 in 10**25
711 714 try:
712 715 node = id
713 716 self.rev(node) # quick search the index
714 717 return node
715 718 except LookupError:
716 719 pass # may be partial hex id
717 720 try:
718 721 # str(rev)
719 722 rev = int(id)
720 723 if str(rev) != id:
721 724 raise ValueError
722 725 if rev < 0:
723 726 rev = len(self) + rev
724 727 if rev < 0 or rev >= len(self):
725 728 raise ValueError
726 729 return self.node(rev)
727 730 except (ValueError, OverflowError):
728 731 pass
729 732 if len(id) == 40:
730 733 try:
731 734 # a full hex nodeid?
732 735 node = bin(id)
733 736 self.rev(node)
734 737 return node
735 738 except (TypeError, LookupError):
736 739 pass
737 740
738 741 def _partialmatch(self, id):
739 742 if id in self._pcache:
740 743 return self._pcache[id]
741 744
742 745 if len(id) < 40:
743 746 try:
744 747 # hex(node)[:...]
745 748 l = len(id) // 2 # grab an even number of digits
746 749 prefix = bin(id[:l * 2])
747 750 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
748 751 nl = [n for n in nl if hex(n).startswith(id)]
749 752 if len(nl) > 0:
750 753 if len(nl) == 1:
751 754 self._pcache[id] = nl[0]
752 755 return nl[0]
753 756 raise LookupError(id, self.indexfile,
754 757 _('ambiguous identifier'))
755 758 return None
756 759 except TypeError:
757 760 pass
758 761
759 762 def lookup(self, id):
760 763 """locate a node based on:
761 764 - revision number or str(revision number)
762 765 - nodeid or subset of hex nodeid
763 766 """
764 767 n = self._match(id)
765 768 if n is not None:
766 769 return n
767 770 n = self._partialmatch(id)
768 771 if n:
769 772 return n
770 773
771 774 raise LookupError(id, self.indexfile, _('no match found'))
772 775
773 776 def cmp(self, node, text):
774 777 """compare text with a given file revision
775 778
776 779 returns True if text is different than what is stored.
777 780 """
778 781 p1, p2 = self.parents(node)
779 782 return hash(text, p1, p2) != node
780 783
781 784 def _addchunk(self, offset, data):
782 785 o, d = self._chunkcache
783 786 # try to add to existing cache
784 787 if o + len(d) == offset and len(d) + len(data) < _chunksize:
785 788 self._chunkcache = o, d + data
786 789 else:
787 790 self._chunkcache = offset, data
788 791
789 792 def _loadchunk(self, offset, length):
790 793 if self._inline:
791 794 df = self.opener(self.indexfile)
792 795 else:
793 796 df = self.opener(self.datafile)
794 797
795 798 readahead = max(65536, length)
796 799 df.seek(offset)
797 800 d = df.read(readahead)
798 801 self._addchunk(offset, d)
799 802 if readahead > length:
800 803 return d[:length]
801 804 return d
802 805
803 806 def _getchunk(self, offset, length):
804 807 o, d = self._chunkcache
805 808 l = len(d)
806 809
807 810 # is it in the cache?
808 811 cachestart = offset - o
809 812 cacheend = cachestart + length
810 813 if cachestart >= 0 and cacheend <= l:
811 814 if cachestart == 0 and cacheend == l:
812 815 return d # avoid a copy
813 816 return d[cachestart:cacheend]
814 817
815 818 return self._loadchunk(offset, length)
816 819
817 820 def _chunkraw(self, startrev, endrev):
818 821 start = self.start(startrev)
819 822 length = self.end(endrev) - start
820 823 if self._inline:
821 824 start += (startrev + 1) * self._io.size
822 825 return self._getchunk(start, length)
823 826
824 827 def _chunk(self, rev):
825 828 return decompress(self._chunkraw(rev, rev))
826 829
827 830 def _chunkbase(self, rev):
828 831 return self._chunk(rev)
829 832
830 833 def _chunkclear(self):
831 834 self._chunkcache = (0, '')
832 835
833 836 def deltaparent(self, rev):
834 837 """return deltaparent of the given revision"""
835 838 base = self.index[rev][3]
836 839 if base == rev:
837 840 return nullrev
838 841 elif self._generaldelta:
839 842 return base
840 843 else:
841 844 return rev - 1
842 845
843 846 def revdiff(self, rev1, rev2):
844 847 """return or calculate a delta between two revisions"""
845 848 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
846 849 return self._chunk(rev2)
847 850
848 851 return mdiff.textdiff(self.revision(self.node(rev1)),
849 852 self.revision(self.node(rev2)))
850 853
851 854 def revision(self, node):
852 855 """return an uncompressed revision of a given node"""
853 856 cachedrev = None
854 857 if node == nullid:
855 858 return ""
856 859 if self._cache:
857 860 if self._cache[0] == node:
858 861 return self._cache[2]
859 862 cachedrev = self._cache[1]
860 863
861 864 # look up what we need to read
862 865 text = None
863 866 rev = self.rev(node)
864 867
865 868 # check rev flags
866 869 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
867 870 raise RevlogError(_('incompatible revision flag %x') %
868 871 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
869 872
870 873 # build delta chain
871 874 chain = []
872 875 index = self.index # for performance
873 876 generaldelta = self._generaldelta
874 877 iterrev = rev
875 878 e = index[iterrev]
876 879 while iterrev != e[3] and iterrev != cachedrev:
877 880 chain.append(iterrev)
878 881 if generaldelta:
879 882 iterrev = e[3]
880 883 else:
881 884 iterrev -= 1
882 885 e = index[iterrev]
883 886 chain.reverse()
884 887 base = iterrev
885 888
886 889 if iterrev == cachedrev:
887 890 # cache hit
888 891 text = self._cache[2]
889 892
890 893 # drop cache to save memory
891 894 self._cache = None
892 895
893 896 self._chunkraw(base, rev)
894 897 if text is None:
895 898 text = self._chunkbase(base)
896 899
897 900 bins = [self._chunk(r) for r in chain]
898 901 text = mdiff.patches(text, bins)
899 902
900 903 text = self._checkhash(text, node, rev)
901 904
902 905 self._cache = (node, rev, text)
903 906 return text
904 907
905 908 def _checkhash(self, text, node, rev):
906 909 p1, p2 = self.parents(node)
907 910 if node != hash(text, p1, p2):
908 911 raise RevlogError(_("integrity check failed on %s:%d")
909 912 % (self.indexfile, rev))
910 913 return text
911 914
912 915 def checkinlinesize(self, tr, fp=None):
913 916 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
914 917 return
915 918
916 919 trinfo = tr.find(self.indexfile)
917 920 if trinfo is None:
918 921 raise RevlogError(_("%s not found in the transaction")
919 922 % self.indexfile)
920 923
921 924 trindex = trinfo[2]
922 925 dataoff = self.start(trindex)
923 926
924 927 tr.add(self.datafile, dataoff)
925 928
926 929 if fp:
927 930 fp.flush()
928 931 fp.close()
929 932
930 933 df = self.opener(self.datafile, 'w')
931 934 try:
932 935 for r in self:
933 936 df.write(self._chunkraw(r, r))
934 937 finally:
935 938 df.close()
936 939
937 940 fp = self.opener(self.indexfile, 'w', atomictemp=True)
938 941 self.version &= ~(REVLOGNGINLINEDATA)
939 942 self._inline = False
940 943 for i in self:
941 944 e = self._io.packentry(self.index[i], self.node, self.version, i)
942 945 fp.write(e)
943 946
944 947 # if we don't call rename, the temp file will never replace the
945 948 # real index
946 949 fp.rename()
947 950
948 951 tr.replace(self.indexfile, trindex * self._io.size)
949 952 self._chunkclear()
950 953
951 954 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
952 955 """add a revision to the log
953 956
954 957 text - the revision data to add
955 958 transaction - the transaction object used for rollback
956 959 link - the linkrev data to add
957 960 p1, p2 - the parent nodeids of the revision
958 961 cachedelta - an optional precomputed delta
959 962 """
960 963 node = hash(text, p1, p2)
961 964 if node in self.nodemap:
962 965 return node
963 966
964 967 dfh = None
965 968 if not self._inline:
966 969 dfh = self.opener(self.datafile, "a")
967 970 ifh = self.opener(self.indexfile, "a+")
968 971 try:
969 972 return self._addrevision(node, text, transaction, link, p1, p2,
970 973 cachedelta, ifh, dfh)
971 974 finally:
972 975 if dfh:
973 976 dfh.close()
974 977 ifh.close()
975 978
976 979 def _addrevision(self, node, text, transaction, link, p1, p2,
977 980 cachedelta, ifh, dfh):
978 981
979 982 btext = [text]
980 983 def buildtext():
981 984 if btext[0] is not None:
982 985 return btext[0]
983 986 # flush any pending writes here so we can read it in revision
984 987 if dfh:
985 988 dfh.flush()
986 989 ifh.flush()
987 990 basetext = self.revision(self.node(cachedelta[0]))
988 991 btext[0] = mdiff.patch(basetext, cachedelta[1])
989 992 chk = hash(btext[0], p1, p2)
990 993 if chk != node:
991 994 raise RevlogError(_("consistency error in delta"))
992 995 return btext[0]
993 996
994 997 def builddelta(rev):
995 998 # can we use the cached delta?
996 999 if cachedelta and cachedelta[0] == rev:
997 1000 delta = cachedelta[1]
998 1001 else:
999 1002 t = buildtext()
1000 1003 ptext = self.revision(self.node(rev))
1001 1004 delta = mdiff.textdiff(ptext, t)
1002 1005 data = compress(delta)
1003 1006 l = len(data[1]) + len(data[0])
1004 1007 basecache = self._basecache
1005 1008 if basecache and basecache[0] == rev:
1006 base = basecache[1]
1009 chainbase = basecache[1]
1007 1010 else:
1008 base = self.chainbase(rev)
1009 dist = l + offset - self.start(base)
1011 chainbase = self.chainbase(rev)
1012 dist = l + offset - self.start(chainbase)
1013 if self._generaldelta:
1014 base = rev
1015 else:
1016 base = chainbase
1010 1017 return dist, l, data, base
1011 1018
1012 1019 curr = len(self)
1013 1020 prev = curr - 1
1014 1021 base = curr
1015 1022 offset = self.end(prev)
1016 1023 flags = 0
1017 1024 d = None
1018 1025 p1r, p2r = self.rev(p1), self.rev(p2)
1019 1026
1020 1027 # should we try to build a delta?
1021 1028 if prev != nullrev:
1022 d = builddelta(prev)
1029 if self._generaldelta:
1030 d = builddelta(p1r)
1031 else:
1032 d = builddelta(prev)
1023 1033 dist, l, data, base = d
1024 1034
1025 1035 # full versions are inserted when the needed deltas
1026 1036 # become comparable to the uncompressed text
1027 1037 if text is None:
1028 1038 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1029 1039 cachedelta[1])
1030 1040 else:
1031 1041 textlen = len(text)
1032 1042 if d is None or dist > textlen * 2:
1033 1043 text = buildtext()
1034 1044 data = compress(text)
1035 1045 l = len(data[1]) + len(data[0])
1036 1046 base = curr
1037 1047
1038 1048 e = (offset_type(offset, flags), l, textlen,
1039 1049 base, link, p1r, p2r, node)
1040 1050 self.index.insert(-1, e)
1041 1051 self.nodemap[node] = curr
1042 1052
1043 1053 entry = self._io.packentry(e, self.node, self.version, curr)
1044 1054 if not self._inline:
1045 1055 transaction.add(self.datafile, offset)
1046 1056 transaction.add(self.indexfile, curr * len(entry))
1047 1057 if data[0]:
1048 1058 dfh.write(data[0])
1049 1059 dfh.write(data[1])
1050 1060 dfh.flush()
1051 1061 ifh.write(entry)
1052 1062 else:
1053 1063 offset += curr * self._io.size
1054 1064 transaction.add(self.indexfile, offset, curr)
1055 1065 ifh.write(entry)
1056 1066 ifh.write(data[0])
1057 1067 ifh.write(data[1])
1058 1068 self.checkinlinesize(transaction, ifh)
1059 1069
1060 1070 if type(text) == str: # only accept immutable objects
1061 1071 self._cache = (node, curr, text)
1062 1072 self._basecache = (curr, base)
1063 1073 return node
1064 1074
1065 1075 def group(self, nodelist, bundler):
1066 1076 """Calculate a delta group, yielding a sequence of changegroup chunks
1067 1077 (strings).
1068 1078
1069 1079 Given a list of changeset revs, return a set of deltas and
1070 1080 metadata corresponding to nodes. The first delta is
1071 1081 first parent(nodelist[0]) -> nodelist[0], the receiver is
1072 1082 guaranteed to have this parent as it has all history before
1073 1083 these changesets. In the case firstparent is nullrev the
1074 1084 changegroup starts with a full revision.
1075 1085 """
1076 1086
1077 1087 revs = sorted([self.rev(n) for n in nodelist])
1078 1088
1079 1089 # if we don't have any revisions touched by these changesets, bail
1080 1090 if not revs:
1081 1091 yield bundler.close()
1082 1092 return
1083 1093
1084 1094 # add the parent of the first rev
1085 1095 p = self.parentrevs(revs[0])[0]
1086 1096 revs.insert(0, p)
1087 1097
1088 1098 # build deltas
1089 1099 for r in xrange(len(revs) - 1):
1090 1100 prev, curr = revs[r], revs[r + 1]
1091 1101 for c in bundler.revchunk(self, curr, prev):
1092 1102 yield c
1093 1103
1094 1104 yield bundler.close()
1095 1105
1096 1106 def addgroup(self, bundle, linkmapper, transaction):
1097 1107 """
1098 1108 add a delta group
1099 1109
1100 1110 given a set of deltas, add them to the revision log. the
1101 1111 first delta is against its parent, which should be in our
1102 1112 log, the rest are against the previous delta.
1103 1113 """
1104 1114
1105 1115 # track the base of the current delta log
1106 1116 node = None
1107 1117
1108 1118 r = len(self)
1109 1119 end = 0
1110 1120 if r:
1111 1121 end = self.end(r - 1)
1112 1122 ifh = self.opener(self.indexfile, "a+")
1113 1123 isize = r * self._io.size
1114 1124 if self._inline:
1115 1125 transaction.add(self.indexfile, end + isize, r)
1116 1126 dfh = None
1117 1127 else:
1118 1128 transaction.add(self.indexfile, isize, r)
1119 1129 transaction.add(self.datafile, end)
1120 1130 dfh = self.opener(self.datafile, "a")
1121 1131
1122 1132 try:
1123 1133 # loop through our set of deltas
1124 1134 chain = None
1125 1135 while 1:
1126 1136 chunkdata = bundle.deltachunk(chain)
1127 1137 if not chunkdata:
1128 1138 break
1129 1139 node = chunkdata['node']
1130 1140 p1 = chunkdata['p1']
1131 1141 p2 = chunkdata['p2']
1132 1142 cs = chunkdata['cs']
1133 1143 deltabase = chunkdata['deltabase']
1134 1144 delta = chunkdata['delta']
1135 1145
1136 1146 link = linkmapper(cs)
1137 1147 if node in self.nodemap:
1138 1148 # this can happen if two branches make the same change
1139 1149 chain = node
1140 1150 continue
1141 1151
1142 1152 for p in (p1, p2):
1143 1153 if not p in self.nodemap:
1144 1154 raise LookupError(p, self.indexfile,
1145 1155 _('unknown parent'))
1146 1156
1147 1157 if deltabase not in self.nodemap:
1148 1158 raise LookupError(deltabase, self.indexfile,
1149 1159 _('unknown delta base'))
1150 1160
1151 1161 baserev = self.rev(deltabase)
1152 1162 chain = self._addrevision(node, None, transaction, link,
1153 1163 p1, p2, (baserev, delta), ifh, dfh)
1154 1164 if not dfh and not self._inline:
1155 1165 # addrevision switched from inline to conventional
1156 1166 # reopen the index
1157 1167 ifh.close()
1158 1168 dfh = self.opener(self.datafile, "a")
1159 1169 ifh = self.opener(self.indexfile, "a")
1160 1170 finally:
1161 1171 if dfh:
1162 1172 dfh.close()
1163 1173 ifh.close()
1164 1174
1165 1175 return node
1166 1176
1167 1177 def strip(self, minlink, transaction):
1168 1178 """truncate the revlog on the first revision with a linkrev >= minlink
1169 1179
1170 1180 This function is called when we're stripping revision minlink and
1171 1181 its descendants from the repository.
1172 1182
1173 1183 We have to remove all revisions with linkrev >= minlink, because
1174 1184 the equivalent changelog revisions will be renumbered after the
1175 1185 strip.
1176 1186
1177 1187 So we truncate the revlog on the first of these revisions, and
1178 1188 trust that the caller has saved the revisions that shouldn't be
1179 1189 removed and that it'll readd them after this truncation.
1180 1190 """
1181 1191 if len(self) == 0:
1182 1192 return
1183 1193
1184 1194 for rev in self:
1185 1195 if self.index[rev][4] >= minlink:
1186 1196 break
1187 1197 else:
1188 1198 return
1189 1199
1190 1200 # first truncate the files on disk
1191 1201 end = self.start(rev)
1192 1202 if not self._inline:
1193 1203 transaction.add(self.datafile, end)
1194 1204 end = rev * self._io.size
1195 1205 else:
1196 1206 end += rev * self._io.size
1197 1207
1198 1208 transaction.add(self.indexfile, end)
1199 1209
1200 1210 # then reset internal state in memory to forget those revisions
1201 1211 self._cache = None
1202 1212 self._chunkclear()
1203 1213 for x in xrange(rev, len(self)):
1204 1214 del self.nodemap[self.node(x)]
1205 1215
1206 1216 del self.index[rev:-1]
1207 1217
1208 1218 def checksize(self):
1209 1219 expected = 0
1210 1220 if len(self):
1211 1221 expected = max(0, self.end(len(self) - 1))
1212 1222
1213 1223 try:
1214 1224 f = self.opener(self.datafile)
1215 1225 f.seek(0, 2)
1216 1226 actual = f.tell()
1217 1227 f.close()
1218 1228 dd = actual - expected
1219 1229 except IOError, inst:
1220 1230 if inst.errno != errno.ENOENT:
1221 1231 raise
1222 1232 dd = 0
1223 1233
1224 1234 try:
1225 1235 f = self.opener(self.indexfile)
1226 1236 f.seek(0, 2)
1227 1237 actual = f.tell()
1228 1238 f.close()
1229 1239 s = self._io.size
1230 1240 i = max(0, actual // s)
1231 1241 di = actual - (i * s)
1232 1242 if self._inline:
1233 1243 databytes = 0
1234 1244 for r in self:
1235 1245 databytes += max(0, self.length(r))
1236 1246 dd = 0
1237 1247 di = actual - len(self) * s - databytes
1238 1248 except IOError, inst:
1239 1249 if inst.errno != errno.ENOENT:
1240 1250 raise
1241 1251 di = 0
1242 1252
1243 1253 return (dd, di)
1244 1254
1245 1255 def files(self):
1246 1256 res = [self.indexfile]
1247 1257 if not self._inline:
1248 1258 res.append(self.datafile)
1249 1259 return res
General Comments 0
You need to be logged in to leave comments. Login now