mercurial/repair.py
author Alexis S. L. Carvalho <alexis@cecm.usp.br>
Sat, 19 Jan 2008 18:01:16 -0200
changeset 5900 1206e3dfc906
parent 5899 d7388ad85511
child 5901 16f4129c19ac
permissions -rw-r--r--
repair.py: nodes are nodes, revs are revs Use "striprev" instead of just "rev" to make the patch a bit less confusing. This is also a bit clearer than just "revnum".

# repair.py - functions for repository repair for mercurial
#
# Copyright 2005, 2006 Chris Mason <mason@suse.com>
# Copyright 2007 Matt Mackall
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.

import changegroup, os
from node import *

def strip(ui, repo, node, backup="all"):
    def limitheads(chlog, stop):
        """return the list of all nodes that have no children"""
        p = {}
        h = []
        stoprev = 0
        if stop in chlog.nodemap:
            stoprev = chlog.rev(stop)

        for r in xrange(chlog.count() - 1, -1, -1):
            n = chlog.node(r)
            if n not in p:
                h.append(n)
            if n == stop:
                break
            if r < stoprev:
                break
            for pn in chlog.parents(n):
                p[pn] = 1
        return h

    def bundle(repo, bases, heads, node, suffix):
        cg = repo.changegroupsubset(bases, heads, 'strip')
        backupdir = repo.join("strip-backup")
        if not os.path.isdir(backupdir):
            os.mkdir(backupdir)
        name = os.path.join(backupdir, "%s-%s" % (short(node), suffix))
        ui.warn("saving bundle to %s\n" % name)
        return changegroup.writebundle(cg, name, "HG10BZ")

    def stripall(striprev):
        mm = repo.changectx(node).manifest()
        seen = {}

        for x in xrange(striprev, repo.changelog.count()):
            for f in repo.changectx(x).files():
                if f in seen:
                    continue
                seen[f] = 1
                if f in mm:
                    filerev = mm[f]
                else:
                    filerev = 0
                seen[f] = filerev
        # we go in two steps here so the strip loop happens in a
        # sensible order.  When stripping many files, this helps keep
        # our disk access patterns under control.
        seen_list = seen.keys()
        seen_list.sort()
        for f in seen_list:
            ff = repo.file(f)
            filerev = seen[f]
            if filerev != 0:
                if filerev in ff.nodemap:
                    filerev = ff.rev(filerev)
                else:
                    filerev = 0
            ff.strip(filerev, striprev)

    chlog = repo.changelog
    # TODO delete the undo files, and handle undo of merge sets
    pp = chlog.parents(node)
    striprev = chlog.rev(node)

    # save is a list of all the branches we are truncating away
    # that we actually want to keep.  changegroup will be used
    # to preserve them and add them back after the truncate
    saveheads = []
    savebases = {}

    heads = limitheads(chlog, node)
    seen = {}

    # search through all the heads, finding those where the revision
    # we want to strip away is an ancestor.  Also look for merges
    # that might be turned into new heads by the strip.
    while heads:
        h = heads.pop()
        n = h
        while True:
            seen[n] = 1
            pp = chlog.parents(n)
            if pp[1] != nullid:
                for p in pp:
                    if chlog.rev(p) > striprev and p not in seen:
                        heads.append(p)
            if pp[0] == nullid:
                break
            if chlog.rev(pp[0]) < striprev:
                break
            n = pp[0]
            if n == node:
                break
        r = chlog.reachable(h, node)
        if node not in r:
            saveheads.append(h)
            for x in r:
                if chlog.rev(x) > striprev:
                    savebases[x] = 1

    # create a changegroup for all the branches we need to keep
    if backup == "all":
        bundle(repo, [node], chlog.heads(), node, 'backup')
    if saveheads:
        chgrpfile = bundle(repo, savebases.keys(), saveheads, node, 'temp')

    stripall(striprev)

    change = chlog.read(node)
    chlog.strip(striprev, striprev)
    repo.manifest.strip(repo.manifest.rev(change[0]), striprev)
    if saveheads:
        ui.status("adding branch\n")
        f = open(chgrpfile, "rb")
        gen = changegroup.readbundle(f, chgrpfile)
        repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile)
        f.close()
        if backup != "strip":
            os.unlink(chgrpfile)