mercurial/repair.py
changeset 4702 18e91c9def0c
equal deleted inserted replaced
4701:d2da07fb5727 4702:18e91c9def0c
       
     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
       
     7 # of the GNU General Public License, incorporated herein by reference.
       
     8 
       
     9 import changegroup, revlog, os, commands
       
    10 
       
    11 def strip(ui, repo, rev, backup="all"):
       
    12     def limitheads(chlog, stop):
       
    13         """return the list of all nodes that have no children"""
       
    14         p = {}
       
    15         h = []
       
    16         stoprev = 0
       
    17         if stop in chlog.nodemap:
       
    18             stoprev = chlog.rev(stop)
       
    19 
       
    20         for r in xrange(chlog.count() - 1, -1, -1):
       
    21             n = chlog.node(r)
       
    22             if n not in p:
       
    23                 h.append(n)
       
    24             if n == stop:
       
    25                 break
       
    26             if r < stoprev:
       
    27                 break
       
    28             for pn in chlog.parents(n):
       
    29                 p[pn] = 1
       
    30         return h
       
    31 
       
    32     def bundle(repo, bases, heads, rev, suffix):
       
    33         cg = repo.changegroupsubset(bases, heads, 'strip')
       
    34         backupdir = repo.join("strip-backup")
       
    35         if not os.path.isdir(backupdir):
       
    36             os.mkdir(backupdir)
       
    37         name = os.path.join(backupdir, "%s-%s" % (revlog.short(rev), suffix))
       
    38         ui.warn("saving bundle to %s\n" % name)
       
    39         return changegroup.writebundle(cg, name, "HG10BZ")
       
    40 
       
    41     def stripall(revnum):
       
    42         mm = repo.changectx(rev).manifest()
       
    43         seen = {}
       
    44 
       
    45         for x in xrange(revnum, repo.changelog.count()):
       
    46             for f in repo.changectx(x).files():
       
    47                 if f in seen:
       
    48                     continue
       
    49                 seen[f] = 1
       
    50                 if f in mm:
       
    51                     filerev = mm[f]
       
    52                 else:
       
    53                     filerev = 0
       
    54                 seen[f] = filerev
       
    55         # we go in two steps here so the strip loop happens in a
       
    56         # sensible order.  When stripping many files, this helps keep
       
    57         # our disk access patterns under control.
       
    58         seen_list = seen.keys()
       
    59         seen_list.sort()
       
    60         for f in seen_list:
       
    61             ff = repo.file(f)
       
    62             filerev = seen[f]
       
    63             if filerev != 0:
       
    64                 if filerev in ff.nodemap:
       
    65                     filerev = ff.rev(filerev)
       
    66                 else:
       
    67                     filerev = 0
       
    68             ff.strip(filerev, revnum)
       
    69 
       
    70     chlog = repo.changelog
       
    71     # TODO delete the undo files, and handle undo of merge sets
       
    72     pp = chlog.parents(rev)
       
    73     revnum = chlog.rev(rev)
       
    74 
       
    75     # save is a list of all the branches we are truncating away
       
    76     # that we actually want to keep.  changegroup will be used
       
    77     # to preserve them and add them back after the truncate
       
    78     saveheads = []
       
    79     savebases = {}
       
    80 
       
    81     heads = limitheads(chlog, rev)
       
    82     seen = {}
       
    83 
       
    84     # search through all the heads, finding those where the revision
       
    85     # we want to strip away is an ancestor.  Also look for merges
       
    86     # that might be turned into new heads by the strip.
       
    87     while heads:
       
    88         h = heads.pop()
       
    89         n = h
       
    90         while True:
       
    91             seen[n] = 1
       
    92             pp = chlog.parents(n)
       
    93             if pp[1] != revlog.nullid:
       
    94                 for p in pp:
       
    95                     if chlog.rev(p) > revnum and p not in seen:
       
    96                         heads.append(p)
       
    97             if pp[0] == revlog.nullid:
       
    98                 break
       
    99             if chlog.rev(pp[0]) < revnum:
       
   100                 break
       
   101             n = pp[0]
       
   102             if n == rev:
       
   103                 break
       
   104         r = chlog.reachable(h, rev)
       
   105         if rev not in r:
       
   106             saveheads.append(h)
       
   107             for x in r:
       
   108                 if chlog.rev(x) > revnum:
       
   109                     savebases[x] = 1
       
   110 
       
   111     # create a changegroup for all the branches we need to keep
       
   112     if backup == "all":
       
   113         bundle(repo, [rev], chlog.heads(), rev, 'backup')
       
   114     if saveheads:
       
   115         chgrpfile = bundle(repo, savebases.keys(), saveheads, rev, 'temp')
       
   116 
       
   117     stripall(revnum)
       
   118 
       
   119     change = chlog.read(rev)
       
   120     chlog.strip(revnum, revnum)
       
   121     repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
       
   122     if saveheads:
       
   123         ui.status("adding branch\n")
       
   124         commands.unbundle(ui, repo, "file:%s" % chgrpfile, update=False)
       
   125         if backup != "strip":
       
   126             os.unlink(chgrpfile)
       
   127