PageRenderTime 50ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/mercurial/repair.py

https://bitbucket.org/mirror/mercurial/
Python | 183 lines | 131 code | 26 blank | 26 comment | 40 complexity | 5aed767be2e7f8d3a7cde26c0e61161e MD5 | raw file
Possible License(s): GPL-2.0
  1. # repair.py - functions for repository repair for mercurial
  2. #
  3. # Copyright 2005, 2006 Chris Mason <mason@suse.com>
  4. # Copyright 2007 Matt Mackall
  5. #
  6. # This software may be used and distributed according to the terms of the
  7. # GNU General Public License version 2 or any later version.
  8. from mercurial import changegroup, exchange
  9. from mercurial.node import short
  10. from mercurial.i18n import _
  11. import errno
  12. def _bundle(repo, bases, heads, node, suffix, compress=True):
  13. """create a bundle with the specified revisions as a backup"""
  14. cg = changegroup.changegroupsubset(repo, bases, heads, 'strip')
  15. backupdir = "strip-backup"
  16. vfs = repo.vfs
  17. if not vfs.isdir(backupdir):
  18. vfs.mkdir(backupdir)
  19. name = "%s/%s-%s.hg" % (backupdir, short(node), suffix)
  20. if compress:
  21. bundletype = "HG10BZ"
  22. else:
  23. bundletype = "HG10UN"
  24. return changegroup.writebundle(cg, name, bundletype, vfs)
  25. def _collectfiles(repo, striprev):
  26. """find out the filelogs affected by the strip"""
  27. files = set()
  28. for x in xrange(striprev, len(repo)):
  29. files.update(repo[x].files())
  30. return sorted(files)
  31. def _collectbrokencsets(repo, files, striprev):
  32. """return the changesets which will be broken by the truncation"""
  33. s = set()
  34. def collectone(revlog):
  35. _, brokenset = revlog.getstrippoint(striprev)
  36. s.update([revlog.linkrev(r) for r in brokenset])
  37. collectone(repo.manifest)
  38. for fname in files:
  39. collectone(repo.file(fname))
  40. return s
  41. def strip(ui, repo, nodelist, backup="all", topic='backup'):
  42. repo = repo.unfiltered()
  43. repo.destroying()
  44. cl = repo.changelog
  45. # TODO handle undo of merge sets
  46. if isinstance(nodelist, str):
  47. nodelist = [nodelist]
  48. striplist = [cl.rev(node) for node in nodelist]
  49. striprev = min(striplist)
  50. keeppartialbundle = backup == 'strip'
  51. # Some revisions with rev > striprev may not be descendants of striprev.
  52. # We have to find these revisions and put them in a bundle, so that
  53. # we can restore them after the truncations.
  54. # To create the bundle we use repo.changegroupsubset which requires
  55. # the list of heads and bases of the set of interesting revisions.
  56. # (head = revision in the set that has no descendant in the set;
  57. # base = revision in the set that has no ancestor in the set)
  58. tostrip = set(striplist)
  59. for rev in striplist:
  60. for desc in cl.descendants([rev]):
  61. tostrip.add(desc)
  62. files = _collectfiles(repo, striprev)
  63. saverevs = _collectbrokencsets(repo, files, striprev)
  64. # compute heads
  65. saveheads = set(saverevs)
  66. for r in xrange(striprev + 1, len(cl)):
  67. if r not in tostrip:
  68. saverevs.add(r)
  69. saveheads.difference_update(cl.parentrevs(r))
  70. saveheads.add(r)
  71. saveheads = [cl.node(r) for r in saveheads]
  72. # compute base nodes
  73. if saverevs:
  74. descendants = set(cl.descendants(saverevs))
  75. saverevs.difference_update(descendants)
  76. savebases = [cl.node(r) for r in saverevs]
  77. stripbases = [cl.node(r) for r in tostrip]
  78. # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
  79. # is much faster
  80. newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
  81. if newbmtarget:
  82. newbmtarget = repo[newbmtarget[0]].node()
  83. else:
  84. newbmtarget = '.'
  85. bm = repo._bookmarks
  86. updatebm = []
  87. for m in bm:
  88. rev = repo[bm[m]].rev()
  89. if rev in tostrip:
  90. updatebm.append(m)
  91. # create a changegroup for all the branches we need to keep
  92. backupfile = None
  93. vfs = repo.vfs
  94. if backup == "all":
  95. backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
  96. repo.ui.status(_("saved backup bundle to %s\n") %
  97. vfs.join(backupfile))
  98. repo.ui.log("backupbundle", "saved backup bundle to %s\n",
  99. vfs.join(backupfile))
  100. if saveheads or savebases:
  101. # do not compress partial bundle if we remove it from disk later
  102. chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
  103. compress=keeppartialbundle)
  104. mfst = repo.manifest
  105. tr = repo.transaction("strip")
  106. offset = len(tr.entries)
  107. try:
  108. tr.startgroup()
  109. cl.strip(striprev, tr)
  110. mfst.strip(striprev, tr)
  111. for fn in files:
  112. repo.file(fn).strip(striprev, tr)
  113. tr.endgroup()
  114. try:
  115. for i in xrange(offset, len(tr.entries)):
  116. file, troffset, ignore = tr.entries[i]
  117. repo.sopener(file, 'a').truncate(troffset)
  118. if troffset == 0:
  119. repo.store.markremoved(file)
  120. tr.close()
  121. except: # re-raises
  122. tr.abort()
  123. raise
  124. if saveheads or savebases:
  125. ui.note(_("adding branch\n"))
  126. f = vfs.open(chgrpfile, "rb")
  127. gen = exchange.readbundle(ui, f, chgrpfile, vfs)
  128. if not repo.ui.verbose:
  129. # silence internal shuffling chatter
  130. repo.ui.pushbuffer()
  131. changegroup.addchangegroup(repo, gen, 'strip',
  132. 'bundle:' + vfs.join(chgrpfile), True)
  133. if not repo.ui.verbose:
  134. repo.ui.popbuffer()
  135. f.close()
  136. if not keeppartialbundle:
  137. vfs.unlink(chgrpfile)
  138. # remove undo files
  139. for undovfs, undofile in repo.undofiles():
  140. try:
  141. undovfs.unlink(undofile)
  142. except OSError, e:
  143. if e.errno != errno.ENOENT:
  144. ui.warn(_('error removing %s: %s\n') %
  145. (undovfs.join(undofile), str(e)))
  146. for m in updatebm:
  147. bm[m] = repo[newbmtarget].node()
  148. bm.write()
  149. except: # re-raises
  150. if backupfile:
  151. ui.warn(_("strip failed, full bundle stored in '%s'\n")
  152. % vfs.join(backupfile))
  153. elif saveheads:
  154. ui.warn(_("strip failed, partial bundle stored in '%s'\n")
  155. % vfs.join(chgrpfile))
  156. raise
  157. repo.destroyed()