##// END OF EJS Templates
narrow: open the transaction sooner when unbundling...
marmoute -
r51085:34db5692 default
parent child Browse files
Show More
@@ -1,354 +1,355 b''
1 1 # narrowbundle2.py - bundle2 extensions for narrow repository support
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import errno
10 10 import struct
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 bundle2,
15 15 changegroup,
16 16 error,
17 17 exchange,
18 18 localrepo,
19 19 narrowspec,
20 20 repair,
21 21 requirements,
22 22 scmutil,
23 23 util,
24 24 wireprototypes,
25 25 )
26 26 from mercurial.utils import stringutil
27 27
28 28 _NARROWACL_SECTION = b'narrowacl'
29 29 _CHANGESPECPART = b'narrow:changespec'
30 30 _RESSPECS = b'narrow:responsespec'
31 31 _SPECPART = b'narrow:spec'
32 32 _SPECPART_INCLUDE = b'include'
33 33 _SPECPART_EXCLUDE = b'exclude'
34 34 _KILLNODESIGNAL = b'KILL'
35 35 _DONESIGNAL = b'DONE'
36 36 _ELIDEDCSHEADER = b'>20s20s20sl' # cset id, p1, p2, len(text)
37 37 _ELIDEDMFHEADER = b'>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
38 38 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
39 39 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
40 40
41 41 # Serve a changegroup for a client with a narrow clone.
42 42 def getbundlechangegrouppart_narrow(
43 43 bundler,
44 44 repo,
45 45 source,
46 46 bundlecaps=None,
47 47 b2caps=None,
48 48 heads=None,
49 49 common=None,
50 50 **kwargs
51 51 ):
52 52 assert repo.ui.configbool(b'experimental', b'narrowservebrokenellipses')
53 53
54 54 cgversions = b2caps.get(b'changegroup')
55 55 cgversions = [
56 56 v
57 57 for v in cgversions
58 58 if v in changegroup.supportedoutgoingversions(repo)
59 59 ]
60 60 if not cgversions:
61 61 raise ValueError(_(b'no common changegroup version'))
62 62 version = max(cgversions)
63 63
64 64 include = sorted(filter(bool, kwargs.get('includepats', [])))
65 65 exclude = sorted(filter(bool, kwargs.get('excludepats', [])))
66 66 generateellipsesbundle2(
67 67 bundler,
68 68 repo,
69 69 include,
70 70 exclude,
71 71 version,
72 72 common,
73 73 heads,
74 74 kwargs.get('depth', None),
75 75 )
76 76
77 77
78 78 def generateellipsesbundle2(
79 79 bundler,
80 80 repo,
81 81 include,
82 82 exclude,
83 83 version,
84 84 common,
85 85 heads,
86 86 depth,
87 87 ):
88 88 match = narrowspec.match(repo.root, include=include, exclude=exclude)
89 89 if depth is not None:
90 90 depth = int(depth)
91 91 if depth < 1:
92 92 raise error.Abort(_(b'depth must be positive, got %d') % depth)
93 93
94 94 heads = set(heads or repo.heads())
95 95 common = set(common or [repo.nullid])
96 96
97 97 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
98 98 repo, common, heads, set(), match, depth=depth
99 99 )
100 100
101 101 repo.ui.debug(b'Found %d relevant revs\n' % len(relevant_nodes))
102 102 if visitnodes:
103 103 packer = changegroup.getbundler(
104 104 version,
105 105 repo,
106 106 matcher=match,
107 107 ellipses=True,
108 108 shallow=depth is not None,
109 109 ellipsisroots=ellipsisroots,
110 110 fullnodes=relevant_nodes,
111 111 )
112 112 cgdata = packer.generate(common, visitnodes, False, b'narrow_widen')
113 113
114 114 part = bundler.newpart(b'changegroup', data=cgdata)
115 115 part.addparam(b'version', version)
116 116 if scmutil.istreemanifest(repo):
117 117 part.addparam(b'treemanifest', b'1')
118 118
119 119
120 120 def generate_ellipses_bundle2_for_widening(
121 121 bundler,
122 122 repo,
123 123 oldmatch,
124 124 newmatch,
125 125 version,
126 126 common,
127 127 known,
128 128 ):
129 129 common = set(common or [repo.nullid])
130 130 # Steps:
131 131 # 1. Send kill for "$known & ::common"
132 132 #
133 133 # 2. Send changegroup for ::common
134 134 #
135 135 # 3. Proceed.
136 136 #
137 137 # In the future, we can send kills for only the specific
138 138 # nodes we know should go away or change shape, and then
139 139 # send a data stream that tells the client something like this:
140 140 #
141 141 # a) apply this changegroup
142 142 # b) apply nodes XXX, YYY, ZZZ that you already have
143 143 # c) goto a
144 144 #
145 145 # until they've built up the full new state.
146 146 knownrevs = {repo.changelog.rev(n) for n in known}
147 147 # TODO: we could send only roots() of this set, and the
148 148 # list of nodes in common, and the client could work out
149 149 # what to strip, instead of us explicitly sending every
150 150 # single node.
151 151 deadrevs = knownrevs
152 152
153 153 def genkills():
154 154 for r in deadrevs:
155 155 yield _KILLNODESIGNAL
156 156 yield repo.changelog.node(r)
157 157 yield _DONESIGNAL
158 158
159 159 bundler.newpart(_CHANGESPECPART, data=genkills())
160 160 newvisit, newfull, newellipsis = exchange._computeellipsis(
161 161 repo, set(), common, knownrevs, newmatch
162 162 )
163 163 if newvisit:
164 164 packer = changegroup.getbundler(
165 165 version,
166 166 repo,
167 167 matcher=newmatch,
168 168 ellipses=True,
169 169 shallow=False,
170 170 ellipsisroots=newellipsis,
171 171 fullnodes=newfull,
172 172 )
173 173 cgdata = packer.generate(common, newvisit, False, b'narrow_widen')
174 174
175 175 part = bundler.newpart(b'changegroup', data=cgdata)
176 176 part.addparam(b'version', version)
177 177 if scmutil.istreemanifest(repo):
178 178 part.addparam(b'treemanifest', b'1')
179 179
180 180
181 181 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
182 182 def _handlechangespec_2(op, inpart):
183 183 # XXX: This bundle2 handling is buggy and should be removed after hg5.2 is
184 184 # released. New servers will send a mandatory bundle2 part named
185 185 # 'Narrowspec' and will send specs as data instead of params.
186 186 # Refer to issue5952 and 6019
187 187 includepats = set(inpart.params.get(_SPECPART_INCLUDE, b'').splitlines())
188 188 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, b'').splitlines())
189 189 narrowspec.validatepatterns(includepats)
190 190 narrowspec.validatepatterns(excludepats)
191 191
192 192 if not requirements.NARROW_REQUIREMENT in op.repo.requirements:
193 193 op.repo.requirements.add(requirements.NARROW_REQUIREMENT)
194 194 scmutil.writereporequirements(op.repo)
195 195 op.repo.setnarrowpats(includepats, excludepats)
196 196 narrowspec.copytoworkingcopy(op.repo)
197 197
198 198
199 199 @bundle2.parthandler(_RESSPECS)
200 200 def _handlenarrowspecs(op, inpart):
201 201 data = inpart.read()
202 202 inc, exc = data.split(b'\0')
203 203 includepats = set(inc.splitlines())
204 204 excludepats = set(exc.splitlines())
205 205 narrowspec.validatepatterns(includepats)
206 206 narrowspec.validatepatterns(excludepats)
207 207
208 208 if requirements.NARROW_REQUIREMENT not in op.repo.requirements:
209 209 op.repo.requirements.add(requirements.NARROW_REQUIREMENT)
210 210 scmutil.writereporequirements(op.repo)
211 211 op.repo.setnarrowpats(includepats, excludepats)
212 212 narrowspec.copytoworkingcopy(op.repo)
213 213
214 214
215 215 @bundle2.parthandler(_CHANGESPECPART)
216 216 def _handlechangespec(op, inpart):
217 217 repo = op.repo
218 218 cl = repo.changelog
219 219
220 220 # changesets which need to be stripped entirely. either they're no longer
221 221 # needed in the new narrow spec, or the server is sending a replacement
222 222 # in the changegroup part.
223 223 clkills = set()
224 224
225 225 # A changespec part contains all the updates to ellipsis nodes
226 226 # that will happen as a result of widening or narrowing a
227 227 # repo. All the changes that this block encounters are ellipsis
228 228 # nodes or flags to kill an existing ellipsis.
229 229 chunksignal = changegroup.readexactly(inpart, 4)
230 230 while chunksignal != _DONESIGNAL:
231 231 if chunksignal == _KILLNODESIGNAL:
232 232 # a node used to be an ellipsis but isn't anymore
233 233 ck = changegroup.readexactly(inpart, 20)
234 234 if cl.hasnode(ck):
235 235 clkills.add(ck)
236 236 else:
237 237 raise error.Abort(
238 238 _(b'unexpected changespec node chunk type: %s') % chunksignal
239 239 )
240 240 chunksignal = changegroup.readexactly(inpart, 4)
241 241
242 242 if clkills:
243 243 # preserve bookmarks that repair.strip() would otherwise strip
244 244 op._bookmarksbackup = repo._bookmarks
245 245
246 246 class dummybmstore(dict):
247 247 def applychanges(self, repo, tr, changes):
248 248 pass
249 249
250 250 localrepo.localrepository._bookmarks.set(repo, dummybmstore())
251 251 chgrpfile = repair.strip(
252 252 op.ui, repo, list(clkills), backup=True, topic=b'widen'
253 253 )
254 254 if chgrpfile:
255 255 op._widen_uninterr = repo.ui.uninterruptible()
256 256 op._widen_uninterr.__enter__()
257 257 # presence of _widen_bundle attribute activates widen handler later
258 258 op._widen_bundle = chgrpfile
259 259 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
260 260 # will currently always be there when using the core+narrowhg server, but
261 261 # other servers may include a changespec part even when not widening (e.g.
262 262 # because we're deepening a shallow repo).
263 263 if util.safehasattr(repo, 'setnewnarrowpats'):
264 op.gettransaction()
264 265 repo.setnewnarrowpats()
265 266
266 267
267 268 def handlechangegroup_widen(op, inpart):
268 269 """Changegroup exchange handler which restores temporarily-stripped nodes"""
269 270 # We saved a bundle with stripped node data we must now restore.
270 271 # This approach is based on mercurial/repair.py@6ee26a53c111.
271 272 repo = op.repo
272 273 ui = op.ui
273 274
274 275 chgrpfile = op._widen_bundle
275 276 del op._widen_bundle
276 277 vfs = repo.vfs
277 278
278 279 ui.note(_(b"adding branch\n"))
279 280 f = vfs.open(chgrpfile, b"rb")
280 281 try:
281 282 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
282 283 # silence internal shuffling chatter
283 284 maybe_silent = (
284 285 ui.silent() if not ui.verbose else util.nullcontextmanager()
285 286 )
286 287 with maybe_silent:
287 288 if isinstance(gen, bundle2.unbundle20):
288 289 with repo.transaction(b'strip') as tr:
289 290 bundle2.processbundle(repo, gen, lambda: tr)
290 291 else:
291 292 gen.apply(
292 293 repo, b'strip', b'bundle:' + vfs.join(chgrpfile), True
293 294 )
294 295 finally:
295 296 f.close()
296 297
297 298 # remove undo files
298 299 for undovfs, undofile in repo.undofiles():
299 300 try:
300 301 undovfs.unlink(undofile)
301 302 except OSError as e:
302 303 if e.errno != errno.ENOENT:
303 304 ui.warn(
304 305 _(b'error removing %s: %s\n')
305 306 % (undovfs.join(undofile), stringutil.forcebytestr(e))
306 307 )
307 308
308 309 # Remove partial backup only if there were no exceptions
309 310 op._widen_uninterr.__exit__(None, None, None)
310 311 vfs.unlink(chgrpfile)
311 312
312 313
313 314 def setup():
314 315 """Enable narrow repo support in bundle2-related extension points."""
315 316 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
316 317
317 318 getbundleargs[b'narrow'] = b'boolean'
318 319 getbundleargs[b'depth'] = b'plain'
319 320 getbundleargs[b'oldincludepats'] = b'csv'
320 321 getbundleargs[b'oldexcludepats'] = b'csv'
321 322 getbundleargs[b'known'] = b'csv'
322 323
323 324 # Extend changegroup serving to handle requests from narrow clients.
324 325 origcgfn = exchange.getbundle2partsmapping[b'changegroup']
325 326
326 327 def wrappedcgfn(*args, **kwargs):
327 328 repo = args[1]
328 329 if repo.ui.has_section(_NARROWACL_SECTION):
329 330 kwargs = exchange.applynarrowacl(repo, kwargs)
330 331
331 332 if kwargs.get('narrow', False) and repo.ui.configbool(
332 333 b'experimental', b'narrowservebrokenellipses'
333 334 ):
334 335 getbundlechangegrouppart_narrow(*args, **kwargs)
335 336 else:
336 337 origcgfn(*args, **kwargs)
337 338
338 339 exchange.getbundle2partsmapping[b'changegroup'] = wrappedcgfn
339 340
340 341 # Extend changegroup receiver so client can fixup after widen requests.
341 342 origcghandler = bundle2.parthandlermapping[b'changegroup']
342 343
343 344 def wrappedcghandler(op, inpart):
344 345 origcghandler(op, inpart)
345 346 if util.safehasattr(op, '_widen_bundle'):
346 347 handlechangegroup_widen(op, inpart)
347 348 if util.safehasattr(op, '_bookmarksbackup'):
348 349 localrepo.localrepository._bookmarks.set(
349 350 op.repo, op._bookmarksbackup
350 351 )
351 352 del op._bookmarksbackup
352 353
353 354 wrappedcghandler.params = origcghandler.params
354 355 bundle2.parthandlermapping[b'changegroup'] = wrappedcghandler
General Comments 0
You need to be logged in to leave comments. Login now