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