repair.py
200 lines
| 6.9 KiB
| text/x-python
|
PythonLexer
/ mercurial / repair.py
|
r4702 | # repair.py - functions for repository repair for mercurial | ||
# | ||||
# Copyright 2005, 2006 Chris Mason <mason@suse.com> | ||||
# Copyright 2007 Matt Mackall | ||||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r4702 | |||
|
r16623 | from mercurial import changegroup, bookmarks | ||
|
r14064 | from mercurial.node import short | ||
from mercurial.i18n import _ | ||||
|
r8312 | import os | ||
|
r16440 | import errno | ||
|
r4702 | |||
|
r15386 | def _bundle(repo, bases, heads, node, suffix, compress=True): | ||
|
r5905 | """create a bundle with the specified revisions as a backup""" | ||
|
r15386 | cg = repo.changegroupsubset(bases, heads, 'strip') | ||
|
r5905 | backupdir = repo.join("strip-backup") | ||
if not os.path.isdir(backupdir): | ||||
os.mkdir(backupdir) | ||||
|
r11333 | name = os.path.join(backupdir, "%s-%s.hg" % (short(node), suffix)) | ||
|
r11791 | if compress: | ||
bundletype = "HG10BZ" | ||||
else: | ||||
bundletype = "HG10UN" | ||||
return changegroup.writebundle(cg, name, bundletype) | ||||
|
r4702 | |||
|
r5910 | def _collectfiles(repo, striprev): | ||
"""find out the filelogs affected by the strip""" | ||||
|
r8462 | files = set() | ||
|
r4702 | |||
|
r6750 | for x in xrange(striprev, len(repo)): | ||
|
r8479 | files.update(repo[x].files()) | ||
|
r5902 | |||
|
r8462 | return sorted(files) | ||
|
r5902 | |||
|
r13702 | def _collectbrokencsets(repo, files, striprev): | ||
"""return the changesets which will be broken by the truncation""" | ||||
|
r13705 | s = set() | ||
|
r13702 | def collectone(revlog): | ||
|
r16628 | linkgen = (revlog.linkrev(i) for i in revlog) | ||
|
r5909 | # find the truncation point of the revlog | ||
|
r16628 | for lrev in linkgen: | ||
|
r13702 | if lrev >= striprev: | ||
|
r5909 | break | ||
|
r13705 | # see if any revision after this point has a linkrev | ||
# less than striprev (those will be broken by strip) | ||||
|
r16628 | for lrev in linkgen: | ||
|
r13705 | if lrev < striprev: | ||
|
r13747 | s.add(lrev) | ||
|
r5909 | |||
|
r13705 | collectone(repo.manifest) | ||
for fname in files: | ||||
collectone(repo.file(fname)) | ||||
|
r5909 | |||
|
r13705 | return s | ||
|
r5909 | |||
|
r16388 | def strip(ui, repo, nodelist, backup="all", topic='backup'): | ||
|
r17013 | # It simplifies the logic around updating the branchheads cache if we only | ||
# have to consider the effect of the stripped revisions and not revisions | ||||
# missing because the cache is out-of-date. | ||||
repo.updatebranchcache() | ||||
|
r5901 | cl = repo.changelog | ||
|
r16237 | # TODO handle undo of merge sets | ||
|
r16252 | if isinstance(nodelist, str): | ||
nodelist = [nodelist] | ||||
striplist = [cl.rev(node) for node in nodelist] | ||||
striprev = min(striplist) | ||||
|
r4702 | |||
|
r17013 | # Generate set of branches who will have nodes stripped. | ||
striprevs = repo.revs("%ld::", striplist) | ||||
stripbranches = set([repo[rev].branch() for rev in striprevs]) | ||||
# Set of potential new heads resulting from the strip. The parents of any | ||||
# node removed could be a new head because the node to be removed could have | ||||
# been the only child of the parent. | ||||
newheadrevs = repo.revs("parents(%ld::) - %ld::", striprevs, striprevs) | ||||
newheadnodes = set([cl.node(rev) for rev in newheadrevs]) | ||||
newheadbranches = set([repo[rev].branch() for rev in newheadrevs]) | ||||
|
r11791 | keeppartialbundle = backup == 'strip' | ||
|
r6147 | # Some revisions with rev > striprev may not be descendants of striprev. | ||
# We have to find these revisions and put them in a bundle, so that | ||||
# we can restore them after the truncations. | ||||
# To create the bundle we use repo.changegroupsubset which requires | ||||
# the list of heads and bases of the set of interesting revisions. | ||||
# (head = revision in the set that has no descendant in the set; | ||||
# base = revision in the set that has no ancestor in the set) | ||||
|
r16252 | tostrip = set(striplist) | ||
for rev in striplist: | ||||
|
r16867 | for desc in cl.descendants([rev]): | ||
|
r16252 | tostrip.add(desc) | ||
|
r13702 | |||
files = _collectfiles(repo, striprev) | ||||
|
r13705 | saverevs = _collectbrokencsets(repo, files, striprev) | ||
|
r13702 | |||
# compute heads | ||||
saveheads = set(saverevs) | ||||
|
r6750 | for r in xrange(striprev + 1, len(cl)): | ||
|
r13702 | if r not in tostrip: | ||
saverevs.add(r) | ||||
saveheads.difference_update(cl.parentrevs(r)) | ||||
saveheads.add(r) | ||||
saveheads = [cl.node(r) for r in saveheads] | ||||
|
r4702 | |||
|
r15386 | # compute base nodes | ||
if saverevs: | ||||
|
r16867 | descendants = set(cl.descendants(saverevs)) | ||
|
r15386 | saverevs.difference_update(descendants) | ||
savebases = [cl.node(r) for r in saverevs] | ||||
|
r16252 | stripbases = [cl.node(r) for r in tostrip] | ||
|
r17410 | newbmtarget = repo.revs('sort(heads((::%ld) - (%ld)), -rev)', | ||
tostrip, tostrip) | ||||
|
r17264 | if newbmtarget: | ||
|
r17796 | newbmtarget = repo[newbmtarget[0]].node() | ||
|
r17264 | else: | ||
newbmtarget = '.' | ||||
|
r4702 | |||
|
r13362 | bm = repo._bookmarks | ||
updatebm = [] | ||||
for m in bm: | ||||
rev = repo[bm[m]].rev() | ||||
if rev in tostrip: | ||||
updatebm.append(m) | ||||
|
r4702 | # create a changegroup for all the branches we need to keep | ||
|
r11197 | backupfile = None | ||
|
r4702 | if backup == "all": | ||
|
r16388 | backupfile = _bundle(repo, stripbases, cl.heads(), node, topic) | ||
|
r11200 | repo.ui.status(_("saved backup bundle to %s\n") % backupfile) | ||
|
r15386 | if saveheads or savebases: | ||
|
r11791 | # do not compress partial bundle if we remove it from disk later | ||
|
r15386 | chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp', | ||
compress=keeppartialbundle) | ||||
|
r4702 | |||
|
r8073 | mfst = repo.manifest | ||
|
r10881 | tr = repo.transaction("strip") | ||
|
r8073 | offset = len(tr.entries) | ||
|
r11197 | try: | ||
tr.startgroup() | ||||
cl.strip(striprev, tr) | ||||
mfst.strip(striprev, tr) | ||||
for fn in files: | ||||
repo.file(fn).strip(striprev, tr) | ||||
tr.endgroup() | ||||
|
r8073 | |||
|
r11197 | try: | ||
for i in xrange(offset, len(tr.entries)): | ||||
file, troffset, ignore = tr.entries[i] | ||||
repo.sopener(file, 'a').truncate(troffset) | ||||
tr.close() | ||||
|
r16705 | except: # re-raises | ||
|
r11197 | tr.abort() | ||
raise | ||||
|
r15386 | if saveheads or savebases: | ||
|
r11202 | ui.note(_("adding branch\n")) | ||
|
r11197 | f = open(chgrpfile, "rb") | ||
gen = changegroup.readbundle(f, chgrpfile) | ||||
|
r11202 | if not repo.ui.verbose: | ||
# silence internal shuffling chatter | ||||
repo.ui.pushbuffer() | ||||
|
r11197 | repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True) | ||
|
r11202 | if not repo.ui.verbose: | ||
repo.ui.popbuffer() | ||||
|
r11197 | f.close() | ||
|
r11791 | if not keeppartialbundle: | ||
|
r11197 | os.unlink(chgrpfile) | ||
|
r13362 | |||
|
r16237 | # remove undo files | ||
for undofile in repo.undofiles(): | ||||
try: | ||||
os.unlink(undofile) | ||||
except OSError, e: | ||||
if e.errno != errno.ENOENT: | ||||
ui.warn(_('error removing %s: %s\n') % (undofile, str(e))) | ||||
|
r13362 | for m in updatebm: | ||
|
r17264 | bm[m] = repo[newbmtarget].node() | ||
|
r13362 | bookmarks.write(repo) | ||
|
r16705 | except: # re-raises | ||
|
r11197 | if backupfile: | ||
|
r11600 | ui.warn(_("strip failed, full bundle stored in '%s'\n") | ||
% backupfile) | ||||
|
r11197 | elif saveheads: | ||
|
r11600 | ui.warn(_("strip failed, partial bundle stored in '%s'\n") | ||
|
r11197 | % chgrpfile) | ||
|
r8073 | raise | ||
|
r4702 | |||
|
r17013 | if len(stripbranches) == 1 and len(newheadbranches) == 1 \ | ||
and stripbranches == newheadbranches: | ||||
repo.destroyed(newheadnodes) | ||||
else: | ||||
# Multiple branches involved in strip. Will allow branchcache to become | ||||
# invalid and later on rebuilt from scratch | ||||
repo.destroyed() | ||||