##// END OF EJS Templates
repair: forbid strip from inside a transaction...
Pierre-Yves David -
r25300:678d0bfd default
parent child Browse files
Show More
@@ -1,225 +1,231 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, exchange, util, bundle2
9 from mercurial import changegroup, exchange, util, bundle2
10 from mercurial.node import short, hex
10 from mercurial.node import short, hex
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 import errno
12 import errno
13
13
14 def _bundle(repo, bases, heads, node, suffix, compress=True):
14 def _bundle(repo, bases, heads, node, suffix, compress=True):
15 """create a bundle with the specified revisions as a backup"""
15 """create a bundle with the specified revisions as a backup"""
16 usebundle2 = (repo.ui.config('experimental', 'bundle2-exp') and
16 usebundle2 = (repo.ui.config('experimental', 'bundle2-exp') and
17 repo.ui.config('experimental', 'strip-bundle2-version'))
17 repo.ui.config('experimental', 'strip-bundle2-version'))
18 if usebundle2:
18 if usebundle2:
19 cgversion = repo.ui.config('experimental', 'strip-bundle2-version')
19 cgversion = repo.ui.config('experimental', 'strip-bundle2-version')
20 if cgversion not in changegroup.packermap:
20 if cgversion not in changegroup.packermap:
21 repo.ui.warn(_('unknown strip-bundle2-version value %r; '
21 repo.ui.warn(_('unknown strip-bundle2-version value %r; '
22 'should be one of %r\n') %
22 'should be one of %r\n') %
23 (cgversion, sorted(changegroup.packermap.keys()),))
23 (cgversion, sorted(changegroup.packermap.keys()),))
24 cgversion = '01'
24 cgversion = '01'
25 usebundle2 = False
25 usebundle2 = False
26 else:
26 else:
27 cgversion = '01'
27 cgversion = '01'
28
28
29 cg = changegroup.changegroupsubset(repo, bases, heads, 'strip',
29 cg = changegroup.changegroupsubset(repo, bases, heads, 'strip',
30 version=cgversion)
30 version=cgversion)
31 backupdir = "strip-backup"
31 backupdir = "strip-backup"
32 vfs = repo.vfs
32 vfs = repo.vfs
33 if not vfs.isdir(backupdir):
33 if not vfs.isdir(backupdir):
34 vfs.mkdir(backupdir)
34 vfs.mkdir(backupdir)
35
35
36 # Include a hash of all the nodes in the filename for uniqueness
36 # Include a hash of all the nodes in the filename for uniqueness
37 hexbases = (hex(n) for n in bases)
37 hexbases = (hex(n) for n in bases)
38 hexheads = (hex(n) for n in heads)
38 hexheads = (hex(n) for n in heads)
39 allcommits = repo.set('%ls::%ls', hexbases, hexheads)
39 allcommits = repo.set('%ls::%ls', hexbases, hexheads)
40 allhashes = sorted(c.hex() for c in allcommits)
40 allhashes = sorted(c.hex() for c in allcommits)
41 totalhash = util.sha1(''.join(allhashes)).hexdigest()
41 totalhash = util.sha1(''.join(allhashes)).hexdigest()
42 name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
42 name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
43
43
44 if usebundle2:
44 if usebundle2:
45 bundletype = "HG20"
45 bundletype = "HG20"
46 elif compress:
46 elif compress:
47 bundletype = "HG10BZ"
47 bundletype = "HG10BZ"
48 else:
48 else:
49 bundletype = "HG10UN"
49 bundletype = "HG10UN"
50 return changegroup.writebundle(repo.ui, cg, name, bundletype, vfs)
50 return changegroup.writebundle(repo.ui, cg, name, bundletype, vfs)
51
51
52 def _collectfiles(repo, striprev):
52 def _collectfiles(repo, striprev):
53 """find out the filelogs affected by the strip"""
53 """find out the filelogs affected by the strip"""
54 files = set()
54 files = set()
55
55
56 for x in xrange(striprev, len(repo)):
56 for x in xrange(striprev, len(repo)):
57 files.update(repo[x].files())
57 files.update(repo[x].files())
58
58
59 return sorted(files)
59 return sorted(files)
60
60
61 def _collectbrokencsets(repo, files, striprev):
61 def _collectbrokencsets(repo, files, striprev):
62 """return the changesets which will be broken by the truncation"""
62 """return the changesets which will be broken by the truncation"""
63 s = set()
63 s = set()
64 def collectone(revlog):
64 def collectone(revlog):
65 _, brokenset = revlog.getstrippoint(striprev)
65 _, brokenset = revlog.getstrippoint(striprev)
66 s.update([revlog.linkrev(r) for r in brokenset])
66 s.update([revlog.linkrev(r) for r in brokenset])
67
67
68 collectone(repo.manifest)
68 collectone(repo.manifest)
69 for fname in files:
69 for fname in files:
70 collectone(repo.file(fname))
70 collectone(repo.file(fname))
71
71
72 return s
72 return s
73
73
74 def strip(ui, repo, nodelist, backup=True, topic='backup'):
74 def strip(ui, repo, nodelist, backup=True, topic='backup'):
75
75
76 # Simple way to maintain backwards compatibility for this
76 # Simple way to maintain backwards compatibility for this
77 # argument.
77 # argument.
78 if backup in ['none', 'strip']:
78 if backup in ['none', 'strip']:
79 backup = False
79 backup = False
80
80
81 repo = repo.unfiltered()
81 repo = repo.unfiltered()
82 repo.destroying()
82 repo.destroying()
83
83
84 cl = repo.changelog
84 cl = repo.changelog
85 # TODO handle undo of merge sets
85 # TODO handle undo of merge sets
86 if isinstance(nodelist, str):
86 if isinstance(nodelist, str):
87 nodelist = [nodelist]
87 nodelist = [nodelist]
88 striplist = [cl.rev(node) for node in nodelist]
88 striplist = [cl.rev(node) for node in nodelist]
89 striprev = min(striplist)
89 striprev = min(striplist)
90
90
91 # Some revisions with rev > striprev may not be descendants of striprev.
91 # Some revisions with rev > striprev may not be descendants of striprev.
92 # We have to find these revisions and put them in a bundle, so that
92 # We have to find these revisions and put them in a bundle, so that
93 # we can restore them after the truncations.
93 # we can restore them after the truncations.
94 # To create the bundle we use repo.changegroupsubset which requires
94 # To create the bundle we use repo.changegroupsubset which requires
95 # the list of heads and bases of the set of interesting revisions.
95 # the list of heads and bases of the set of interesting revisions.
96 # (head = revision in the set that has no descendant in the set;
96 # (head = revision in the set that has no descendant in the set;
97 # base = revision in the set that has no ancestor in the set)
97 # base = revision in the set that has no ancestor in the set)
98 tostrip = set(striplist)
98 tostrip = set(striplist)
99 for rev in striplist:
99 for rev in striplist:
100 for desc in cl.descendants([rev]):
100 for desc in cl.descendants([rev]):
101 tostrip.add(desc)
101 tostrip.add(desc)
102
102
103 files = _collectfiles(repo, striprev)
103 files = _collectfiles(repo, striprev)
104 saverevs = _collectbrokencsets(repo, files, striprev)
104 saverevs = _collectbrokencsets(repo, files, striprev)
105
105
106 # compute heads
106 # compute heads
107 saveheads = set(saverevs)
107 saveheads = set(saverevs)
108 for r in xrange(striprev + 1, len(cl)):
108 for r in xrange(striprev + 1, len(cl)):
109 if r not in tostrip:
109 if r not in tostrip:
110 saverevs.add(r)
110 saverevs.add(r)
111 saveheads.difference_update(cl.parentrevs(r))
111 saveheads.difference_update(cl.parentrevs(r))
112 saveheads.add(r)
112 saveheads.add(r)
113 saveheads = [cl.node(r) for r in saveheads]
113 saveheads = [cl.node(r) for r in saveheads]
114
114
115 # compute base nodes
115 # compute base nodes
116 if saverevs:
116 if saverevs:
117 descendants = set(cl.descendants(saverevs))
117 descendants = set(cl.descendants(saverevs))
118 saverevs.difference_update(descendants)
118 saverevs.difference_update(descendants)
119 savebases = [cl.node(r) for r in saverevs]
119 savebases = [cl.node(r) for r in saverevs]
120 stripbases = [cl.node(r) for r in tostrip]
120 stripbases = [cl.node(r) for r in tostrip]
121
121
122 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
122 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
123 # is much faster
123 # is much faster
124 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
124 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
125 if newbmtarget:
125 if newbmtarget:
126 newbmtarget = repo[newbmtarget.first()].node()
126 newbmtarget = repo[newbmtarget.first()].node()
127 else:
127 else:
128 newbmtarget = '.'
128 newbmtarget = '.'
129
129
130 bm = repo._bookmarks
130 bm = repo._bookmarks
131 updatebm = []
131 updatebm = []
132 for m in bm:
132 for m in bm:
133 rev = repo[bm[m]].rev()
133 rev = repo[bm[m]].rev()
134 if rev in tostrip:
134 if rev in tostrip:
135 updatebm.append(m)
135 updatebm.append(m)
136
136
137 # create a changegroup for all the branches we need to keep
137 # create a changegroup for all the branches we need to keep
138 backupfile = None
138 backupfile = None
139 vfs = repo.vfs
139 vfs = repo.vfs
140 node = nodelist[-1]
140 node = nodelist[-1]
141 if backup:
141 if backup:
142 backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
142 backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
143 repo.ui.status(_("saved backup bundle to %s\n") %
143 repo.ui.status(_("saved backup bundle to %s\n") %
144 vfs.join(backupfile))
144 vfs.join(backupfile))
145 repo.ui.log("backupbundle", "saved backup bundle to %s\n",
145 repo.ui.log("backupbundle", "saved backup bundle to %s\n",
146 vfs.join(backupfile))
146 vfs.join(backupfile))
147 if saveheads or savebases:
147 if saveheads or savebases:
148 # do not compress partial bundle if we remove it from disk later
148 # do not compress partial bundle if we remove it from disk later
149 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
149 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
150 compress=False)
150 compress=False)
151
151
152 mfst = repo.manifest
152 mfst = repo.manifest
153
153
154 curtr = repo.currenttransaction()
155 if curtr is not None:
156 del curtr # avoid carrying reference to transaction for nothing
157 msg = _('programming error: cannot strip from inside a transaction')
158 raise util.Abort(msg, hint=_('contact your extension maintainer'))
159
154 tr = repo.transaction("strip")
160 tr = repo.transaction("strip")
155 offset = len(tr.entries)
161 offset = len(tr.entries)
156
162
157 try:
163 try:
158 tr.startgroup()
164 tr.startgroup()
159 cl.strip(striprev, tr)
165 cl.strip(striprev, tr)
160 mfst.strip(striprev, tr)
166 mfst.strip(striprev, tr)
161 for fn in files:
167 for fn in files:
162 repo.file(fn).strip(striprev, tr)
168 repo.file(fn).strip(striprev, tr)
163 tr.endgroup()
169 tr.endgroup()
164
170
165 try:
171 try:
166 for i in xrange(offset, len(tr.entries)):
172 for i in xrange(offset, len(tr.entries)):
167 file, troffset, ignore = tr.entries[i]
173 file, troffset, ignore = tr.entries[i]
168 repo.svfs(file, 'a').truncate(troffset)
174 repo.svfs(file, 'a').truncate(troffset)
169 if troffset == 0:
175 if troffset == 0:
170 repo.store.markremoved(file)
176 repo.store.markremoved(file)
171 tr.close()
177 tr.close()
172 except: # re-raises
178 except: # re-raises
173 tr.abort()
179 tr.abort()
174 raise
180 raise
175
181
176 if saveheads or savebases:
182 if saveheads or savebases:
177 ui.note(_("adding branch\n"))
183 ui.note(_("adding branch\n"))
178 f = vfs.open(chgrpfile, "rb")
184 f = vfs.open(chgrpfile, "rb")
179 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
185 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
180 if not repo.ui.verbose:
186 if not repo.ui.verbose:
181 # silence internal shuffling chatter
187 # silence internal shuffling chatter
182 repo.ui.pushbuffer()
188 repo.ui.pushbuffer()
183 if isinstance(gen, bundle2.unbundle20):
189 if isinstance(gen, bundle2.unbundle20):
184 tr = repo.transaction('strip')
190 tr = repo.transaction('strip')
185 tr.hookargs = {'source': 'strip',
191 tr.hookargs = {'source': 'strip',
186 'url': 'bundle:' + vfs.join(chgrpfile)}
192 'url': 'bundle:' + vfs.join(chgrpfile)}
187 try:
193 try:
188 bundle2.processbundle(repo, gen, lambda: tr)
194 bundle2.processbundle(repo, gen, lambda: tr)
189 tr.close()
195 tr.close()
190 finally:
196 finally:
191 tr.release()
197 tr.release()
192 else:
198 else:
193 changegroup.addchangegroup(repo, gen, 'strip',
199 changegroup.addchangegroup(repo, gen, 'strip',
194 'bundle:' + vfs.join(chgrpfile),
200 'bundle:' + vfs.join(chgrpfile),
195 True)
201 True)
196 if not repo.ui.verbose:
202 if not repo.ui.verbose:
197 repo.ui.popbuffer()
203 repo.ui.popbuffer()
198 f.close()
204 f.close()
199
205
200 # remove undo files
206 # remove undo files
201 for undovfs, undofile in repo.undofiles():
207 for undovfs, undofile in repo.undofiles():
202 try:
208 try:
203 undovfs.unlink(undofile)
209 undovfs.unlink(undofile)
204 except OSError, e:
210 except OSError, e:
205 if e.errno != errno.ENOENT:
211 if e.errno != errno.ENOENT:
206 ui.warn(_('error removing %s: %s\n') %
212 ui.warn(_('error removing %s: %s\n') %
207 (undovfs.join(undofile), str(e)))
213 (undovfs.join(undofile), str(e)))
208
214
209 for m in updatebm:
215 for m in updatebm:
210 bm[m] = repo[newbmtarget].node()
216 bm[m] = repo[newbmtarget].node()
211 bm.write()
217 bm.write()
212 except: # re-raises
218 except: # re-raises
213 if backupfile:
219 if backupfile:
214 ui.warn(_("strip failed, full bundle stored in '%s'\n")
220 ui.warn(_("strip failed, full bundle stored in '%s'\n")
215 % vfs.join(backupfile))
221 % vfs.join(backupfile))
216 elif saveheads:
222 elif saveheads:
217 ui.warn(_("strip failed, partial bundle stored in '%s'\n")
223 ui.warn(_("strip failed, partial bundle stored in '%s'\n")
218 % vfs.join(chgrpfile))
224 % vfs.join(chgrpfile))
219 raise
225 raise
220 else:
226 else:
221 if saveheads or savebases:
227 if saveheads or savebases:
222 # Remove partial backup only if there were no exceptions
228 # Remove partial backup only if there were no exceptions
223 vfs.unlink(chgrpfile)
229 vfs.unlink(chgrpfile)
224
230
225 repo.destroyed()
231 repo.destroyed()
@@ -1,90 +1,109 b''
1
1
2 $ cat << EOF > buggylocking.py
2 $ cat << EOF > buggylocking.py
3 > """A small extension that acquire locks in the wrong order
3 > """A small extension that acquire locks in the wrong order
4 > """
4 > """
5 >
5 >
6 > from mercurial import cmdutil
6 > from mercurial import cmdutil, repair
7 >
7 >
8 > cmdtable = {}
8 > cmdtable = {}
9 > command = cmdutil.command(cmdtable)
9 > command = cmdutil.command(cmdtable)
10 >
10 >
11 > @command('buggylocking', [], '')
11 > @command('buggylocking', [], '')
12 > def buggylocking(ui, repo):
12 > def buggylocking(ui, repo):
13 > tr = repo.transaction('buggy')
13 > tr = repo.transaction('buggy')
14 > lo = repo.lock()
14 > lo = repo.lock()
15 > wl = repo.wlock()
15 > wl = repo.wlock()
16 > wl.release()
16 > wl.release()
17 > lo.release()
17 > lo.release()
18 >
18 >
19 > @command('properlocking', [], '')
19 > @command('properlocking', [], '')
20 > def properlocking(ui, repo):
20 > def properlocking(ui, repo):
21 > """check that reentrance is fine"""
21 > """check that reentrance is fine"""
22 > wl = repo.wlock()
22 > wl = repo.wlock()
23 > lo = repo.lock()
23 > lo = repo.lock()
24 > tr = repo.transaction('proper')
24 > tr = repo.transaction('proper')
25 > tr2 = repo.transaction('proper')
25 > tr2 = repo.transaction('proper')
26 > lo2 = repo.lock()
26 > lo2 = repo.lock()
27 > wl2 = repo.wlock()
27 > wl2 = repo.wlock()
28 > wl2.release()
28 > wl2.release()
29 > lo2.release()
29 > lo2.release()
30 > tr2.close()
30 > tr2.close()
31 > tr.close()
31 > tr.close()
32 > lo.release()
32 > lo.release()
33 > wl.release()
33 > wl.release()
34 >
34 >
35 > @command('nowaitlocking', [], '')
35 > @command('nowaitlocking', [], '')
36 > def nowaitlocking(ui, repo):
36 > def nowaitlocking(ui, repo):
37 > lo = repo.lock()
37 > lo = repo.lock()
38 > wl = repo.wlock(wait=False)
38 > wl = repo.wlock(wait=False)
39 > wl.release()
39 > wl.release()
40 > lo.release()
40 > lo.release()
41 >
42 > @command('stripintr', [], '')
43 > def stripintr(ui, repo):
44 > lo = repo.lock()
45 > tr = repo.transaction('foobar')
46 > try:
47 > repair.strip(repo.ui, repo, [repo['.'].node()])
48 > finally:
49 > lo.release()
41 > EOF
50 > EOF
42
51
43 $ cat << EOF >> $HGRCPATH
52 $ cat << EOF >> $HGRCPATH
44 > [extensions]
53 > [extensions]
45 > buggylocking=$TESTTMP/buggylocking.py
54 > buggylocking=$TESTTMP/buggylocking.py
46 > [devel]
55 > [devel]
47 > all-warnings=1
56 > all-warnings=1
48 > EOF
57 > EOF
49
58
50 $ hg init lock-checker
59 $ hg init lock-checker
51 $ cd lock-checker
60 $ cd lock-checker
52 $ hg buggylocking
61 $ hg buggylocking
53 devel-warn: transaction with no lock at: $TESTTMP/buggylocking.py:11 (buggylocking)
62 devel-warn: transaction with no lock at: $TESTTMP/buggylocking.py:11 (buggylocking)
54 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:13 (buggylocking)
63 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:13 (buggylocking)
55 $ cat << EOF >> $HGRCPATH
64 $ cat << EOF >> $HGRCPATH
56 > [devel]
65 > [devel]
57 > all=0
66 > all=0
58 > check-locks=1
67 > check-locks=1
59 > EOF
68 > EOF
60 $ hg buggylocking
69 $ hg buggylocking
61 devel-warn: transaction with no lock at: $TESTTMP/buggylocking.py:11 (buggylocking)
70 devel-warn: transaction with no lock at: $TESTTMP/buggylocking.py:11 (buggylocking)
62 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:13 (buggylocking)
71 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:13 (buggylocking)
63 $ hg buggylocking --traceback
72 $ hg buggylocking --traceback
64 devel-warn: transaction with no lock at:
73 devel-warn: transaction with no lock at:
65 */hg:* in * (glob)
74 */hg:* in * (glob)
66 */mercurial/dispatch.py:* in run (glob)
75 */mercurial/dispatch.py:* in run (glob)
67 */mercurial/dispatch.py:* in dispatch (glob)
76 */mercurial/dispatch.py:* in dispatch (glob)
68 */mercurial/dispatch.py:* in _runcatch (glob)
77 */mercurial/dispatch.py:* in _runcatch (glob)
69 */mercurial/dispatch.py:* in _dispatch (glob)
78 */mercurial/dispatch.py:* in _dispatch (glob)
70 */mercurial/dispatch.py:* in runcommand (glob)
79 */mercurial/dispatch.py:* in runcommand (glob)
71 */mercurial/dispatch.py:* in _runcommand (glob)
80 */mercurial/dispatch.py:* in _runcommand (glob)
72 */mercurial/dispatch.py:* in checkargs (glob)
81 */mercurial/dispatch.py:* in checkargs (glob)
73 */mercurial/dispatch.py:* in <lambda> (glob)
82 */mercurial/dispatch.py:* in <lambda> (glob)
74 */mercurial/util.py:* in check (glob)
83 */mercurial/util.py:* in check (glob)
75 $TESTTMP/buggylocking.py:* in buggylocking (glob)
84 $TESTTMP/buggylocking.py:* in buggylocking (glob)
76 devel-warn: "wlock" acquired after "lock" at:
85 devel-warn: "wlock" acquired after "lock" at:
77 */hg:* in * (glob)
86 */hg:* in * (glob)
78 */mercurial/dispatch.py:* in run (glob)
87 */mercurial/dispatch.py:* in run (glob)
79 */mercurial/dispatch.py:* in dispatch (glob)
88 */mercurial/dispatch.py:* in dispatch (glob)
80 */mercurial/dispatch.py:* in _runcatch (glob)
89 */mercurial/dispatch.py:* in _runcatch (glob)
81 */mercurial/dispatch.py:* in _dispatch (glob)
90 */mercurial/dispatch.py:* in _dispatch (glob)
82 */mercurial/dispatch.py:* in runcommand (glob)
91 */mercurial/dispatch.py:* in runcommand (glob)
83 */mercurial/dispatch.py:* in _runcommand (glob)
92 */mercurial/dispatch.py:* in _runcommand (glob)
84 */mercurial/dispatch.py:* in checkargs (glob)
93 */mercurial/dispatch.py:* in checkargs (glob)
85 */mercurial/dispatch.py:* in <lambda> (glob)
94 */mercurial/dispatch.py:* in <lambda> (glob)
86 */mercurial/util.py:* in check (glob)
95 */mercurial/util.py:* in check (glob)
87 $TESTTMP/buggylocking.py:* in buggylocking (glob)
96 $TESTTMP/buggylocking.py:* in buggylocking (glob)
88 $ hg properlocking
97 $ hg properlocking
89 $ hg nowaitlocking
98 $ hg nowaitlocking
99
100 $ echo a > a
101 $ hg add a
102 $ hg commit -m a
103 $ hg stripintr
104 saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob)
105 abort: programming error: cannot strip from inside a transaction
106 (contact your extension maintainer)
107 [255]
108
90 $ cd ..
109 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now