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