##// END OF EJS Templates
strip: make query to get new bookmark target cheaper...
Siddharth Agarwal -
r18040:fe8caf28 default
parent child Browse files
Show More
@@ -1,201 +1,203 b''
1 1 # repair.py - functions for repository repair for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 # Copyright 2007 Matt Mackall
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from mercurial import changegroup
10 10 from mercurial.node import short
11 11 from mercurial.i18n import _
12 12 import os
13 13 import errno
14 14
15 15 def _bundle(repo, bases, heads, node, suffix, compress=True):
16 16 """create a bundle with the specified revisions as a backup"""
17 17 cg = repo.changegroupsubset(bases, heads, 'strip')
18 18 backupdir = repo.join("strip-backup")
19 19 if not os.path.isdir(backupdir):
20 20 os.mkdir(backupdir)
21 21 name = os.path.join(backupdir, "%s-%s.hg" % (short(node), suffix))
22 22 if compress:
23 23 bundletype = "HG10BZ"
24 24 else:
25 25 bundletype = "HG10UN"
26 26 return changegroup.writebundle(cg, name, bundletype)
27 27
28 28 def _collectfiles(repo, striprev):
29 29 """find out the filelogs affected by the strip"""
30 30 files = set()
31 31
32 32 for x in xrange(striprev, len(repo)):
33 33 files.update(repo[x].files())
34 34
35 35 return sorted(files)
36 36
37 37 def _collectbrokencsets(repo, files, striprev):
38 38 """return the changesets which will be broken by the truncation"""
39 39 s = set()
40 40 def collectone(revlog):
41 41 linkgen = (revlog.linkrev(i) for i in revlog)
42 42 # find the truncation point of the revlog
43 43 for lrev in linkgen:
44 44 if lrev >= striprev:
45 45 break
46 46 # see if any revision after this point has a linkrev
47 47 # less than striprev (those will be broken by strip)
48 48 for lrev in linkgen:
49 49 if lrev < striprev:
50 50 s.add(lrev)
51 51
52 52 collectone(repo.manifest)
53 53 for fname in files:
54 54 collectone(repo.file(fname))
55 55
56 56 return s
57 57
58 58 def strip(ui, repo, nodelist, backup="all", topic='backup'):
59 59 repo = repo.unfiltered()
60 60 # It simplifies the logic around updating the branchheads cache if we only
61 61 # have to consider the effect of the stripped revisions and not revisions
62 62 # missing because the cache is out-of-date.
63 63 repo.updatebranchcache()
64 64
65 65 cl = repo.changelog
66 66 # TODO handle undo of merge sets
67 67 if isinstance(nodelist, str):
68 68 nodelist = [nodelist]
69 69 striplist = [cl.rev(node) for node in nodelist]
70 70 striprev = min(striplist)
71 71
72 72 # Generate set of branches who will have nodes stripped.
73 73 striprevs = repo.revs("%ld::", striplist)
74 74 stripbranches = set([repo[rev].branch() for rev in striprevs])
75 75
76 76 # Set of potential new heads resulting from the strip. The parents of any
77 77 # node removed could be a new head because the node to be removed could have
78 78 # been the only child of the parent.
79 79 newheadrevs = repo.revs("parents(%ld::) - %ld::", striprevs, striprevs)
80 80 newheadnodes = set([cl.node(rev) for rev in newheadrevs])
81 81 newheadbranches = set([repo[rev].branch() for rev in newheadrevs])
82 82
83 83 keeppartialbundle = backup == 'strip'
84 84
85 85 # Some revisions with rev > striprev may not be descendants of striprev.
86 86 # We have to find these revisions and put them in a bundle, so that
87 87 # we can restore them after the truncations.
88 88 # To create the bundle we use repo.changegroupsubset which requires
89 89 # the list of heads and bases of the set of interesting revisions.
90 90 # (head = revision in the set that has no descendant in the set;
91 91 # base = revision in the set that has no ancestor in the set)
92 92 tostrip = set(striplist)
93 93 for rev in striplist:
94 94 for desc in cl.descendants([rev]):
95 95 tostrip.add(desc)
96 96
97 97 files = _collectfiles(repo, striprev)
98 98 saverevs = _collectbrokencsets(repo, files, striprev)
99 99
100 100 # compute heads
101 101 saveheads = set(saverevs)
102 102 for r in xrange(striprev + 1, len(cl)):
103 103 if r not in tostrip:
104 104 saverevs.add(r)
105 105 saveheads.difference_update(cl.parentrevs(r))
106 106 saveheads.add(r)
107 107 saveheads = [cl.node(r) for r in saveheads]
108 108
109 109 # compute base nodes
110 110 if saverevs:
111 111 descendants = set(cl.descendants(saverevs))
112 112 saverevs.difference_update(descendants)
113 113 savebases = [cl.node(r) for r in saverevs]
114 114 stripbases = [cl.node(r) for r in tostrip]
115 newbmtarget = repo.revs('sort(heads((::%ld) - (%ld)), -rev)',
116 tostrip, tostrip)
115
116 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
117 # is much faster
118 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
117 119 if newbmtarget:
118 120 newbmtarget = repo[newbmtarget[0]].node()
119 121 else:
120 122 newbmtarget = '.'
121 123
122 124 bm = repo._bookmarks
123 125 updatebm = []
124 126 for m in bm:
125 127 rev = repo[bm[m]].rev()
126 128 if rev in tostrip:
127 129 updatebm.append(m)
128 130
129 131 # create a changegroup for all the branches we need to keep
130 132 backupfile = None
131 133 if backup == "all":
132 134 backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
133 135 repo.ui.status(_("saved backup bundle to %s\n") % backupfile)
134 136 if saveheads or savebases:
135 137 # do not compress partial bundle if we remove it from disk later
136 138 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
137 139 compress=keeppartialbundle)
138 140
139 141 mfst = repo.manifest
140 142
141 143 tr = repo.transaction("strip")
142 144 offset = len(tr.entries)
143 145
144 146 try:
145 147 tr.startgroup()
146 148 cl.strip(striprev, tr)
147 149 mfst.strip(striprev, tr)
148 150 for fn in files:
149 151 repo.file(fn).strip(striprev, tr)
150 152 tr.endgroup()
151 153
152 154 try:
153 155 for i in xrange(offset, len(tr.entries)):
154 156 file, troffset, ignore = tr.entries[i]
155 157 repo.sopener(file, 'a').truncate(troffset)
156 158 tr.close()
157 159 except: # re-raises
158 160 tr.abort()
159 161 raise
160 162
161 163 if saveheads or savebases:
162 164 ui.note(_("adding branch\n"))
163 165 f = open(chgrpfile, "rb")
164 166 gen = changegroup.readbundle(f, chgrpfile)
165 167 if not repo.ui.verbose:
166 168 # silence internal shuffling chatter
167 169 repo.ui.pushbuffer()
168 170 repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True)
169 171 if not repo.ui.verbose:
170 172 repo.ui.popbuffer()
171 173 f.close()
172 174 if not keeppartialbundle:
173 175 os.unlink(chgrpfile)
174 176
175 177 # remove undo files
176 178 for undofile in repo.undofiles():
177 179 try:
178 180 os.unlink(undofile)
179 181 except OSError, e:
180 182 if e.errno != errno.ENOENT:
181 183 ui.warn(_('error removing %s: %s\n') % (undofile, str(e)))
182 184
183 185 for m in updatebm:
184 186 bm[m] = repo[newbmtarget].node()
185 187 bm.write()
186 188 except: # re-raises
187 189 if backupfile:
188 190 ui.warn(_("strip failed, full bundle stored in '%s'\n")
189 191 % backupfile)
190 192 elif saveheads:
191 193 ui.warn(_("strip failed, partial bundle stored in '%s'\n")
192 194 % chgrpfile)
193 195 raise
194 196
195 197 if len(stripbranches) == 1 and len(newheadbranches) == 1 \
196 198 and stripbranches == newheadbranches:
197 199 repo.destroyed(newheadnodes)
198 200 else:
199 201 # Multiple branches involved in strip. Will allow branchcache to become
200 202 # invalid and later on rebuilt from scratch
201 203 repo.destroyed()
General Comments 0
You need to be logged in to leave comments. Login now