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