121
|
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, incorporated herein by reference.
|
|
8
|
|
9 import changegroup
|
|
10 from node import nullrev, short
|
|
11 from i18n import _
|
|
12 import os
|
|
13
|
|
14 def _bundle(repo, bases, heads, node, suffix, extranodes=None):
|
|
15 """create a bundle with the specified revisions as a backup"""
|
|
16 cg = repo.changegroupsubset(bases, heads, 'strip', extranodes)
|
|
17 backupdir = repo.join("strip-backup")
|
|
18 if not os.path.isdir(backupdir):
|
|
19 os.mkdir(backupdir)
|
|
20 name = os.path.join(backupdir, "%s-%s" % (short(node), suffix))
|
|
21 repo.ui.warn(_("saving bundle to %s\n") % name)
|
|
22 return changegroup.writebundle(cg, name, "HG10BZ")
|
|
23
|
|
24 def _collectfiles(repo, striprev):
|
|
25 """find out the filelogs affected by the strip"""
|
|
26 files = set()
|
|
27
|
|
28 for x in xrange(striprev, len(repo)):
|
|
29 files.update(repo[x].files())
|
|
30
|
|
31 return sorted(files)
|
|
32
|
|
33 def _collectextranodes(repo, files, link):
|
|
34 """return the nodes that have to be saved before the strip"""
|
|
35 def collectone(revlog):
|
|
36 extra = []
|
|
37 startrev = count = len(revlog)
|
|
38 # find the truncation point of the revlog
|
|
39 for i in xrange(count):
|
|
40 lrev = revlog.linkrev(i)
|
|
41 if lrev >= link:
|
|
42 startrev = i + 1
|
|
43 break
|
|
44
|
|
45 # see if any revision after that point has a linkrev less than link
|
|
46 # (we have to manually save these guys)
|
|
47 for i in xrange(startrev, count):
|
|
48 node = revlog.node(i)
|
|
49 lrev = revlog.linkrev(i)
|
|
50 if lrev < link:
|
|
51 extra.append((node, cl.node(lrev)))
|
|
52
|
|
53 return extra
|
|
54
|
|
55 extranodes = {}
|
|
56 cl = repo.changelog
|
|
57 extra = collectone(repo.manifest)
|
|
58 if extra:
|
|
59 extranodes[1] = extra
|
|
60 for fname in files:
|
|
61 f = repo.file(fname)
|
|
62 extra = collectone(f)
|
|
63 if extra:
|
|
64 extranodes[fname] = extra
|
|
65
|
|
66 return extranodes
|
|
67
|
|
68 def strip(ui, repo, node, backup="all"):
|
|
69 cl = repo.changelog
|
|
70 # TODO delete the undo files, and handle undo of merge sets
|
|
71 striprev = cl.rev(node)
|
|
72
|
|
73 # Some revisions with rev > striprev may not be descendants of striprev.
|
|
74 # We have to find these revisions and put them in a bundle, so that
|
|
75 # we can restore them after the truncations.
|
|
76 # To create the bundle we use repo.changegroupsubset which requires
|
|
77 # the list of heads and bases of the set of interesting revisions.
|
|
78 # (head = revision in the set that has no descendant in the set;
|
|
79 # base = revision in the set that has no ancestor in the set)
|
|
80 tostrip = set((striprev,))
|
|
81 saveheads = set()
|
|
82 savebases = []
|
|
83 for r in xrange(striprev + 1, len(cl)):
|
|
84 parents = cl.parentrevs(r)
|
|
85 if parents[0] in tostrip or parents[1] in tostrip:
|
|
86 # r is a descendant of striprev
|
|
87 tostrip.add(r)
|
|
88 # if this is a merge and one of the parents does not descend
|
|
89 # from striprev, mark that parent as a savehead.
|
|
90 if parents[1] != nullrev:
|
|
91 for p in parents:
|
|
92 if p not in tostrip and p > striprev:
|
|
93 saveheads.add(p)
|
|
94 else:
|
|
95 # if no parents of this revision will be stripped, mark it as
|
|
96 # a savebase
|
|
97 if parents[0] < striprev and parents[1] < striprev:
|
|
98 savebases.append(cl.node(r))
|
|
99
|
|
100 saveheads.difference_update(parents)
|
|
101 saveheads.add(r)
|
|
102
|
|
103 saveheads = [cl.node(r) for r in saveheads]
|
|
104 files = _collectfiles(repo, striprev)
|
|
105
|
|
106 extranodes = _collectextranodes(repo, files, striprev)
|
|
107
|
|
108 # create a changegroup for all the branches we need to keep
|
|
109 if backup == "all":
|
|
110 _bundle(repo, [node], cl.heads(), node, 'backup')
|
|
111 if saveheads or extranodes:
|
|
112 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
|
|
113 extranodes)
|
|
114
|
|
115 mfst = repo.manifest
|
|
116
|
|
117 tr = repo.transaction()
|
|
118 offset = len(tr.entries)
|
|
119
|
|
120 tr.startgroup()
|
|
121 cl.strip(striprev, tr)
|
|
122 mfst.strip(striprev, tr)
|
|
123 for fn in files:
|
|
124 repo.file(fn).strip(striprev, tr)
|
|
125 tr.endgroup()
|
|
126
|
|
127 try:
|
|
128 for i in xrange(offset, len(tr.entries)):
|
|
129 file, troffset, ignore = tr.entries[i]
|
|
130 repo.sopener(file, 'a').truncate(troffset)
|
|
131 tr.close()
|
|
132 except:
|
|
133 tr.abort()
|
|
134 raise
|
|
135
|
|
136 if saveheads or extranodes:
|
|
137 ui.status(_("adding branch\n"))
|
|
138 f = open(chgrpfile, "rb")
|
|
139 gen = changegroup.readbundle(f, chgrpfile)
|
|
140 repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True)
|
|
141 f.close()
|
|
142 if backup != "strip":
|
|
143 os.unlink(chgrpfile)
|
|
144
|