##// END OF EJS Templates
narrow: move wire proto capabilities to narrowwirepeer...
Martin von Zweigbergk -
r39565:e82da0fc default
parent child Browse files
Show More
@@ -1,333 +1,332 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 23 extensions,
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 NARROWCAP = 'narrow'
35 ELLIPSESCAP = 'ellipses'
36 35 _NARROWACL_SECTION = 'narrowhgacl'
37 36 _CHANGESPECPART = NARROWCAP + ':changespec'
38 37 _SPECPART = NARROWCAP + ':spec'
39 38 _SPECPART_INCLUDE = 'include'
40 39 _SPECPART_EXCLUDE = 'exclude'
41 40 _KILLNODESIGNAL = 'KILL'
42 41 _DONESIGNAL = 'DONE'
43 42 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
44 43 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
45 44 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
46 45 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
47 46
48 47 # When advertising capabilities, always include narrow clone support.
49 48 def getrepocaps_narrow(orig, repo, **kwargs):
50 49 caps = orig(repo, **kwargs)
51 50 caps[NARROWCAP] = ['v0']
52 51 return caps
53 52
54 53 def getbundlechangegrouppart_widen(bundler, repo, source, bundlecaps=None,
55 54 b2caps=None, heads=None, common=None,
56 55 **kwargs):
57 56 """Handling changegroup changegroup generation on the server when user
58 57 is widening their narrowspec"""
59 58
60 59 cgversions = b2caps.get('changegroup')
61 60 if cgversions: # 3.1 and 3.2 ship with an empty value
62 61 cgversions = [v for v in cgversions
63 62 if v in changegroup.supportedoutgoingversions(repo)]
64 63 if not cgversions:
65 64 raise ValueError(_('no common changegroup version'))
66 65 version = max(cgversions)
67 66 else:
68 67 raise ValueError(_("server does not advertise changegroup version,"
69 68 " can't negotiate support for ellipsis nodes"))
70 69
71 70 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
72 71 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
73 72 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
74 73 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
75 74 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
76 75 common = set(common or [nullid])
77 76
78 77 if (oldinclude != include or oldexclude != exclude):
79 78 common = repo.revs("::%ln", common)
80 79 commonnodes = set()
81 80 cl = repo.changelog
82 81 for c in common:
83 82 commonnodes.add(cl.node(c))
84 83 if commonnodes:
85 84 # XXX: we should only send the filelogs (and treemanifest). user
86 85 # already has the changelog and manifest
87 86 packer = changegroup.getbundler(version, repo,
88 87 filematcher=newmatch,
89 88 fullnodes=commonnodes)
90 89 cgdata = packer.generate(set([nullid]), list(commonnodes), False,
91 90 source)
92 91
93 92 part = bundler.newpart('changegroup', data=cgdata)
94 93 part.addparam('version', version)
95 94 if 'treemanifest' in repo.requirements:
96 95 part.addparam('treemanifest', '1')
97 96
98 97 # Serve a changegroup for a client with a narrow clone.
99 98 def getbundlechangegrouppart_narrow(bundler, repo, source,
100 99 bundlecaps=None, b2caps=None, heads=None,
101 100 common=None, **kwargs):
102 101 assert repo.ui.configbool('experimental', 'narrowservebrokenellipses')
103 102
104 103 cgversions = b2caps.get('changegroup')
105 104 if cgversions: # 3.1 and 3.2 ship with an empty value
106 105 cgversions = [v for v in cgversions
107 106 if v in changegroup.supportedoutgoingversions(repo)]
108 107 if not cgversions:
109 108 raise ValueError(_('no common changegroup version'))
110 109 version = max(cgversions)
111 110 else:
112 111 raise ValueError(_("server does not advertise changegroup version,"
113 112 " can't negotiate support for ellipsis nodes"))
114 113
115 114 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
116 115 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
117 116 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
118 117
119 118 depth = kwargs.get(r'depth', None)
120 119 if depth is not None:
121 120 depth = int(depth)
122 121 if depth < 1:
123 122 raise error.Abort(_('depth must be positive, got %d') % depth)
124 123
125 124 heads = set(heads or repo.heads())
126 125 common = set(common or [nullid])
127 126 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
128 127 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
129 128 known = {bin(n) for n in kwargs.get(r'known', [])}
130 129 if known and (oldinclude != include or oldexclude != exclude):
131 130 # Steps:
132 131 # 1. Send kill for "$known & ::common"
133 132 #
134 133 # 2. Send changegroup for ::common
135 134 #
136 135 # 3. Proceed.
137 136 #
138 137 # In the future, we can send kills for only the specific
139 138 # nodes we know should go away or change shape, and then
140 139 # send a data stream that tells the client something like this:
141 140 #
142 141 # a) apply this changegroup
143 142 # b) apply nodes XXX, YYY, ZZZ that you already have
144 143 # c) goto a
145 144 #
146 145 # until they've built up the full new state.
147 146 # Convert to revnums and intersect with "common". The client should
148 147 # have made it a subset of "common" already, but let's be safe.
149 148 known = set(repo.revs("%ln & ::%ln", known, common))
150 149 # TODO: we could send only roots() of this set, and the
151 150 # list of nodes in common, and the client could work out
152 151 # what to strip, instead of us explicitly sending every
153 152 # single node.
154 153 deadrevs = known
155 154 def genkills():
156 155 for r in deadrevs:
157 156 yield _KILLNODESIGNAL
158 157 yield repo.changelog.node(r)
159 158 yield _DONESIGNAL
160 159 bundler.newpart(_CHANGESPECPART, data=genkills())
161 160 newvisit, newfull, newellipsis = exchange._computeellipsis(
162 161 repo, set(), common, known, newmatch)
163 162 if newvisit:
164 163 packer = changegroup.getbundler(version, repo,
165 164 filematcher=newmatch,
166 165 ellipses=True,
167 166 shallow=depth is not None,
168 167 ellipsisroots=newellipsis,
169 168 fullnodes=newfull)
170 169 cgdata = packer.generate(common, newvisit, False, source)
171 170
172 171 part = bundler.newpart('changegroup', data=cgdata)
173 172 part.addparam('version', version)
174 173 if 'treemanifest' in repo.requirements:
175 174 part.addparam('treemanifest', '1')
176 175
177 176 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
178 177 repo, common, heads, set(), newmatch, depth=depth)
179 178
180 179 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
181 180 if visitnodes:
182 181 packer = changegroup.getbundler(version, repo,
183 182 filematcher=newmatch,
184 183 ellipses=True,
185 184 shallow=depth is not None,
186 185 ellipsisroots=ellipsisroots,
187 186 fullnodes=relevant_nodes)
188 187 cgdata = packer.generate(common, visitnodes, False, source)
189 188
190 189 part = bundler.newpart('changegroup', data=cgdata)
191 190 part.addparam('version', version)
192 191 if 'treemanifest' in repo.requirements:
193 192 part.addparam('treemanifest', '1')
194 193
195 194 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
196 195 def _handlechangespec_2(op, inpart):
197 196 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
198 197 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
199 198 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
200 199 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
201 200 op.repo._writerequirements()
202 201 op.repo.setnarrowpats(includepats, excludepats)
203 202
204 203 @bundle2.parthandler(_CHANGESPECPART)
205 204 def _handlechangespec(op, inpart):
206 205 repo = op.repo
207 206 cl = repo.changelog
208 207
209 208 # changesets which need to be stripped entirely. either they're no longer
210 209 # needed in the new narrow spec, or the server is sending a replacement
211 210 # in the changegroup part.
212 211 clkills = set()
213 212
214 213 # A changespec part contains all the updates to ellipsis nodes
215 214 # that will happen as a result of widening or narrowing a
216 215 # repo. All the changes that this block encounters are ellipsis
217 216 # nodes or flags to kill an existing ellipsis.
218 217 chunksignal = changegroup.readexactly(inpart, 4)
219 218 while chunksignal != _DONESIGNAL:
220 219 if chunksignal == _KILLNODESIGNAL:
221 220 # a node used to be an ellipsis but isn't anymore
222 221 ck = changegroup.readexactly(inpart, 20)
223 222 if cl.hasnode(ck):
224 223 clkills.add(ck)
225 224 else:
226 225 raise error.Abort(
227 226 _('unexpected changespec node chunk type: %s') % chunksignal)
228 227 chunksignal = changegroup.readexactly(inpart, 4)
229 228
230 229 if clkills:
231 230 # preserve bookmarks that repair.strip() would otherwise strip
232 231 bmstore = repo._bookmarks
233 232 class dummybmstore(dict):
234 233 def applychanges(self, repo, tr, changes):
235 234 pass
236 235 def recordchange(self, tr): # legacy version
237 236 pass
238 237 repo._bookmarks = dummybmstore()
239 238 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
240 239 topic='widen')
241 240 repo._bookmarks = bmstore
242 241 if chgrpfile:
243 242 op._widen_uninterr = repo.ui.uninterruptable()
244 243 op._widen_uninterr.__enter__()
245 244 # presence of _widen_bundle attribute activates widen handler later
246 245 op._widen_bundle = chgrpfile
247 246 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
248 247 # will currently always be there when using the core+narrowhg server, but
249 248 # other servers may include a changespec part even when not widening (e.g.
250 249 # because we're deepening a shallow repo).
251 250 if util.safehasattr(repo, 'setnewnarrowpats'):
252 251 repo.setnewnarrowpats()
253 252
254 253 def handlechangegroup_widen(op, inpart):
255 254 """Changegroup exchange handler which restores temporarily-stripped nodes"""
256 255 # We saved a bundle with stripped node data we must now restore.
257 256 # This approach is based on mercurial/repair.py@6ee26a53c111.
258 257 repo = op.repo
259 258 ui = op.ui
260 259
261 260 chgrpfile = op._widen_bundle
262 261 del op._widen_bundle
263 262 vfs = repo.vfs
264 263
265 264 ui.note(_("adding branch\n"))
266 265 f = vfs.open(chgrpfile, "rb")
267 266 try:
268 267 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
269 268 if not ui.verbose:
270 269 # silence internal shuffling chatter
271 270 ui.pushbuffer()
272 271 if isinstance(gen, bundle2.unbundle20):
273 272 with repo.transaction('strip') as tr:
274 273 bundle2.processbundle(repo, gen, lambda: tr)
275 274 else:
276 275 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
277 276 if not ui.verbose:
278 277 ui.popbuffer()
279 278 finally:
280 279 f.close()
281 280
282 281 # remove undo files
283 282 for undovfs, undofile in repo.undofiles():
284 283 try:
285 284 undovfs.unlink(undofile)
286 285 except OSError as e:
287 286 if e.errno != errno.ENOENT:
288 287 ui.warn(_('error removing %s: %s\n') %
289 288 (undovfs.join(undofile), stringutil.forcebytestr(e)))
290 289
291 290 # Remove partial backup only if there were no exceptions
292 291 op._widen_uninterr.__exit__(None, None, None)
293 292 vfs.unlink(chgrpfile)
294 293
295 294 def setup():
296 295 """Enable narrow repo support in bundle2-related extension points."""
297 296 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
298 297
299 298 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
300 299
301 300 getbundleargs['narrow'] = 'boolean'
302 301 getbundleargs['widen'] = 'boolean'
303 302 getbundleargs['depth'] = 'plain'
304 303 getbundleargs['oldincludepats'] = 'csv'
305 304 getbundleargs['oldexcludepats'] = 'csv'
306 305 getbundleargs['includepats'] = 'csv'
307 306 getbundleargs['excludepats'] = 'csv'
308 307 getbundleargs['known'] = 'csv'
309 308
310 309 # Extend changegroup serving to handle requests from narrow clients.
311 310 origcgfn = exchange.getbundle2partsmapping['changegroup']
312 311 def wrappedcgfn(*args, **kwargs):
313 312 repo = args[1]
314 313 if repo.ui.has_section(_NARROWACL_SECTION):
315 314 kwargs = exchange.applynarrowacl(repo, kwargs)
316 315
317 316 if (kwargs.get(r'narrow', False) and
318 317 repo.ui.configbool('experimental', 'narrowservebrokenellipses')):
319 318 getbundlechangegrouppart_narrow(*args, **kwargs)
320 319 elif kwargs.get(r'widen', False) and kwargs.get(r'narrow', False):
321 320 getbundlechangegrouppart_widen(*args, **kwargs)
322 321 else:
323 322 origcgfn(*args, **kwargs)
324 323 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
325 324
326 325 # Extend changegroup receiver so client can fixup after widen requests.
327 326 origcghandler = bundle2.parthandlermapping['changegroup']
328 327 def wrappedcghandler(op, inpart):
329 328 origcghandler(op, inpart)
330 329 if util.safehasattr(op, '_widen_bundle'):
331 330 handlechangegroup_widen(op, inpart)
332 331 wrappedcghandler.params = origcghandler.params
333 332 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
@@ -1,452 +1,452 b''
1 1 # narrowcommands.py - command modifications for narrowhg extension
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 from __future__ import absolute_import
8 8
9 9 import itertools
10 10 import os
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 cmdutil,
15 15 commands,
16 16 discovery,
17 17 encoding,
18 18 error,
19 19 exchange,
20 20 extensions,
21 21 hg,
22 22 merge,
23 23 narrowspec,
24 24 node,
25 25 pycompat,
26 26 registrar,
27 27 repair,
28 28 repository,
29 29 repoview,
30 30 sparse,
31 31 util,
32 32 )
33 33
34 34 from . import (
35 narrowbundle2,
35 narrowwirepeer,
36 36 )
37 37
38 38 table = {}
39 39 command = registrar.command(table)
40 40
41 41 def setup():
42 42 """Wraps user-facing mercurial commands with narrow-aware versions."""
43 43
44 44 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
45 45 entry[1].append(('', 'narrow', None,
46 46 _("create a narrow clone of select files")))
47 47 entry[1].append(('', 'depth', '',
48 48 _("limit the history fetched by distance from heads")))
49 49 entry[1].append(('', 'narrowspec', '',
50 50 _("read narrowspecs from file")))
51 51 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
52 52 if 'sparse' not in extensions.enabled():
53 53 entry[1].append(('', 'include', [],
54 54 _("specifically fetch this file/directory")))
55 55 entry[1].append(
56 56 ('', 'exclude', [],
57 57 _("do not fetch this file/directory, even if included")))
58 58
59 59 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
60 60 entry[1].append(('', 'depth', '',
61 61 _("limit the history fetched by distance from heads")))
62 62
63 63 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
64 64
65 65 def expandpull(pullop, includepats, excludepats):
66 66 if not narrowspec.needsexpansion(includepats):
67 67 return includepats, excludepats
68 68
69 69 heads = pullop.heads or pullop.rheads
70 70 includepats, excludepats = pullop.remote.expandnarrow(
71 71 includepats, excludepats, heads)
72 72 pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % (
73 73 includepats, excludepats))
74 74 return set(includepats), set(excludepats)
75 75
76 76 def clonenarrowcmd(orig, ui, repo, *args, **opts):
77 77 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
78 78 opts = pycompat.byteskwargs(opts)
79 79 wrappedextraprepare = util.nullcontextmanager()
80 80 opts_narrow = opts['narrow']
81 81 narrowspecfile = opts['narrowspec']
82 82
83 83 if narrowspecfile:
84 84 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
85 85 ui.status(_("reading narrowspec from '%s'\n") % filepath)
86 86 try:
87 87 fdata = util.readfile(filepath)
88 88 except IOError as inst:
89 89 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
90 90 (filepath, encoding.strtolocal(inst.strerror)))
91 91
92 92 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
93 93 if profiles:
94 94 raise error.Abort(_("cannot specify other files using '%include' in"
95 95 " narrowspec"))
96 96
97 97 # narrowspec is passed so we should assume that user wants narrow clone
98 98 opts_narrow = True
99 99 opts['include'].extend(includes)
100 100 opts['exclude'].extend(excludes)
101 101
102 102 if opts_narrow:
103 103 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
104 104 # Create narrow spec patterns from clone flags
105 105 includepats = narrowspec.parsepatterns(opts['include'])
106 106 excludepats = narrowspec.parsepatterns(opts['exclude'])
107 107
108 108 # If necessary, ask the server to expand the narrowspec.
109 109 includepats, excludepats = expandpull(
110 110 pullop, includepats, excludepats)
111 111
112 112 if not includepats and excludepats:
113 113 # If nothing was included, we assume the user meant to include
114 114 # everything, except what they asked to exclude.
115 115 includepats = {'path:.'}
116 116
117 117 pullop.repo.setnarrowpats(includepats, excludepats)
118 118
119 119 # This will populate 'includepats' etc with the values from the
120 120 # narrowspec we just saved.
121 121 orig(pullop, kwargs)
122 122
123 123 if opts.get('depth'):
124 124 kwargs['depth'] = opts['depth']
125 125 wrappedextraprepare = extensions.wrappedfunction(exchange,
126 126 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
127 127
128 128 def pullnarrow(orig, repo, *args, **kwargs):
129 129 if opts_narrow:
130 130 repo.requirements.add(repository.NARROW_REQUIREMENT)
131 131 repo._writerequirements()
132 132
133 133 return orig(repo, *args, **kwargs)
134 134
135 135 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
136 136
137 137 with wrappedextraprepare, wrappedpull:
138 138 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
139 139
140 140 def pullnarrowcmd(orig, ui, repo, *args, **opts):
141 141 """Wraps pull command to allow modifying narrow spec."""
142 142 wrappedextraprepare = util.nullcontextmanager()
143 143 if repository.NARROW_REQUIREMENT in repo.requirements:
144 144
145 145 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
146 146 orig(pullop, kwargs)
147 147 if opts.get(r'depth'):
148 148 kwargs['depth'] = opts[r'depth']
149 149 wrappedextraprepare = extensions.wrappedfunction(exchange,
150 150 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
151 151
152 152 with wrappedextraprepare:
153 153 return orig(ui, repo, *args, **opts)
154 154
155 155 def archivenarrowcmd(orig, ui, repo, *args, **opts):
156 156 """Wraps archive command to narrow the default includes."""
157 157 if repository.NARROW_REQUIREMENT in repo.requirements:
158 158 repo_includes, repo_excludes = repo.narrowpats
159 159 includes = set(opts.get(r'include', []))
160 160 excludes = set(opts.get(r'exclude', []))
161 161 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
162 162 includes, excludes, repo_includes, repo_excludes)
163 163 if includes:
164 164 opts[r'include'] = includes
165 165 if excludes:
166 166 opts[r'exclude'] = excludes
167 167 return orig(ui, repo, *args, **opts)
168 168
169 169 def pullbundle2extraprepare(orig, pullop, kwargs):
170 170 repo = pullop.repo
171 171 if repository.NARROW_REQUIREMENT not in repo.requirements:
172 172 return orig(pullop, kwargs)
173 173
174 if narrowbundle2.NARROWCAP not in pullop.remote.capabilities():
174 if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
175 175 raise error.Abort(_("server doesn't support narrow clones"))
176 176 orig(pullop, kwargs)
177 177 kwargs['narrow'] = True
178 178 include, exclude = repo.narrowpats
179 179 kwargs['oldincludepats'] = include
180 180 kwargs['oldexcludepats'] = exclude
181 181 kwargs['includepats'] = include
182 182 kwargs['excludepats'] = exclude
183 183 # calculate known nodes only in ellipses cases because in non-ellipses cases
184 184 # we have all the nodes
185 if narrowbundle2.ELLIPSESCAP in pullop.remote.capabilities():
185 if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
186 186 kwargs['known'] = [node.hex(ctx.node()) for ctx in
187 187 repo.set('::%ln', pullop.common)
188 188 if ctx.node() != node.nullid]
189 189 if not kwargs['known']:
190 190 # Mercurial serializes an empty list as '' and deserializes it as
191 191 # [''], so delete it instead to avoid handling the empty string on
192 192 # the server.
193 193 del kwargs['known']
194 194
195 195 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
196 196 pullbundle2extraprepare)
197 197
198 198 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
199 199 newincludes, newexcludes, force):
200 200 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
201 201 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
202 202
203 203 # This is essentially doing "hg outgoing" to find all local-only
204 204 # commits. We will then check that the local-only commits don't
205 205 # have any changes to files that will be untracked.
206 206 unfi = repo.unfiltered()
207 207 outgoing = discovery.findcommonoutgoing(unfi, remote,
208 208 commoninc=commoninc)
209 209 ui.status(_('looking for local changes to affected paths\n'))
210 210 localnodes = []
211 211 for n in itertools.chain(outgoing.missing, outgoing.excluded):
212 212 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
213 213 localnodes.append(n)
214 214 revstostrip = unfi.revs('descendants(%ln)', localnodes)
215 215 hiddenrevs = repoview.filterrevs(repo, 'visible')
216 216 visibletostrip = list(repo.changelog.node(r)
217 217 for r in (revstostrip - hiddenrevs))
218 218 if visibletostrip:
219 219 ui.status(_('The following changeset(s) or their ancestors have '
220 220 'local changes not on the remote:\n'))
221 221 maxnodes = 10
222 222 if ui.verbose or len(visibletostrip) <= maxnodes:
223 223 for n in visibletostrip:
224 224 ui.status('%s\n' % node.short(n))
225 225 else:
226 226 for n in visibletostrip[:maxnodes]:
227 227 ui.status('%s\n' % node.short(n))
228 228 ui.status(_('...and %d more, use --verbose to list all\n') %
229 229 (len(visibletostrip) - maxnodes))
230 230 if not force:
231 231 raise error.Abort(_('local changes found'),
232 232 hint=_('use --force-delete-local-changes to '
233 233 'ignore'))
234 234
235 235 with ui.uninterruptable():
236 236 if revstostrip:
237 237 tostrip = [unfi.changelog.node(r) for r in revstostrip]
238 238 if repo['.'].node() in tostrip:
239 239 # stripping working copy, so move to a different commit first
240 240 urev = max(repo.revs('(::%n) - %ln + null',
241 241 repo['.'].node(), visibletostrip))
242 242 hg.clean(repo, urev)
243 243 repair.strip(ui, unfi, tostrip, topic='narrow')
244 244
245 245 todelete = []
246 246 for f, f2, size in repo.store.datafiles():
247 247 if f.startswith('data/'):
248 248 file = f[5:-2]
249 249 if not newmatch(file):
250 250 todelete.append(f)
251 251 elif f.startswith('meta/'):
252 252 dir = f[5:-13]
253 253 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
254 254 include = True
255 255 for d in dirs:
256 256 visit = newmatch.visitdir(d)
257 257 if not visit:
258 258 include = False
259 259 break
260 260 if visit == 'all':
261 261 break
262 262 if not include:
263 263 todelete.append(f)
264 264
265 265 repo.destroying()
266 266
267 267 with repo.transaction("narrowing"):
268 268 for f in todelete:
269 269 ui.status(_('deleting %s\n') % f)
270 270 util.unlinkpath(repo.svfs.join(f))
271 271 repo.store.markremoved(f)
272 272
273 273 for f in repo.dirstate:
274 274 if not newmatch(f):
275 275 repo.dirstate.drop(f)
276 276 repo.wvfs.unlinkpath(f)
277 277 repo.setnarrowpats(newincludes, newexcludes)
278 278
279 279 repo.destroyed()
280 280
281 281 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
282 282 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
283 283
284 284 # TODO(martinvonz): Get expansion working with widening/narrowing.
285 285 if narrowspec.needsexpansion(newincludes):
286 286 raise error.Abort('Expansion not yet supported on pull')
287 287
288 288 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
289 289 orig(pullop, kwargs)
290 290 # The old{in,ex}cludepats have already been set by orig()
291 291 kwargs['includepats'] = newincludes
292 292 kwargs['excludepats'] = newexcludes
293 293 kwargs['widen'] = True
294 294 wrappedextraprepare = extensions.wrappedfunction(exchange,
295 295 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
296 296
297 297 # define a function that narrowbundle2 can call after creating the
298 298 # backup bundle, but before applying the bundle from the server
299 299 def setnewnarrowpats():
300 300 repo.setnarrowpats(newincludes, newexcludes)
301 301 repo.setnewnarrowpats = setnewnarrowpats
302 302
303 303 with ui.uninterruptable():
304 304 ds = repo.dirstate
305 305 p1, p2 = ds.p1(), ds.p2()
306 306 with ds.parentchange():
307 307 ds.setparents(node.nullid, node.nullid)
308 308 common = commoninc[0]
309 309 with wrappedextraprepare:
310 310 exchange.pull(repo, remote, heads=common)
311 311 with ds.parentchange():
312 312 ds.setparents(p1, p2)
313 313
314 314 repo.setnewnarrowpats()
315 315 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
316 316 addgaction = actions['g'].append
317 317
318 318 mf = repo['.'].manifest().matches(newmatch)
319 319 for f, fn in mf.iteritems():
320 320 if f not in repo.dirstate:
321 321 addgaction((f, (mf.flags(f), False),
322 322 "add from widened narrow clone"))
323 323
324 324 merge.applyupdates(repo, actions, wctx=repo[None],
325 325 mctx=repo['.'], overwrite=False)
326 326 merge.recordupdates(repo, actions, branchmerge=False)
327 327
328 328 # TODO(rdamazio): Make new matcher format and update description
329 329 @command('tracked',
330 330 [('', 'addinclude', [], _('new paths to include')),
331 331 ('', 'removeinclude', [], _('old paths to no longer include')),
332 332 ('', 'addexclude', [], _('new paths to exclude')),
333 333 ('', 'import-rules', '', _('import narrowspecs from a file')),
334 334 ('', 'removeexclude', [], _('old paths to no longer exclude')),
335 335 ('', 'clear', False, _('whether to replace the existing narrowspec')),
336 336 ('', 'force-delete-local-changes', False,
337 337 _('forces deletion of local changes when narrowing')),
338 338 ] + commands.remoteopts,
339 339 _('[OPTIONS]... [REMOTE]'),
340 340 inferrepo=True)
341 341 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
342 342 """show or change the current narrowspec
343 343
344 344 With no argument, shows the current narrowspec entries, one per line. Each
345 345 line will be prefixed with 'I' or 'X' for included or excluded patterns,
346 346 respectively.
347 347
348 348 The narrowspec is comprised of expressions to match remote files and/or
349 349 directories that should be pulled into your client.
350 350 The narrowspec has *include* and *exclude* expressions, with excludes always
351 351 trumping includes: that is, if a file matches an exclude expression, it will
352 352 be excluded even if it also matches an include expression.
353 353 Excluding files that were never included has no effect.
354 354
355 355 Each included or excluded entry is in the format described by
356 356 'hg help patterns'.
357 357
358 358 The options allow you to add or remove included and excluded expressions.
359 359
360 360 If --clear is specified, then all previous includes and excludes are DROPPED
361 361 and replaced by the new ones specified to --addinclude and --addexclude.
362 362 If --clear is specified without any further options, the narrowspec will be
363 363 empty and will not match any files.
364 364 """
365 365 opts = pycompat.byteskwargs(opts)
366 366 if repository.NARROW_REQUIREMENT not in repo.requirements:
367 367 ui.warn(_('The narrow command is only supported on respositories cloned'
368 368 ' with --narrow.\n'))
369 369 return 1
370 370
371 371 # Before supporting, decide whether it "hg tracked --clear" should mean
372 372 # tracking no paths or all paths.
373 373 if opts['clear']:
374 374 ui.warn(_('The --clear option is not yet supported.\n'))
375 375 return 1
376 376
377 377 # import rules from a file
378 378 newrules = opts.get('import_rules')
379 379 if newrules:
380 380 try:
381 381 filepath = os.path.join(pycompat.getcwd(), newrules)
382 382 fdata = util.readfile(filepath)
383 383 except IOError as inst:
384 384 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
385 385 (filepath, encoding.strtolocal(inst.strerror)))
386 386 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
387 387 'narrow')
388 388 if profiles:
389 389 raise error.Abort(_("including other spec files using '%include' "
390 390 "is not supported in narrowspec"))
391 391 opts['addinclude'].extend(includepats)
392 392 opts['addexclude'].extend(excludepats)
393 393
394 394 if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']):
395 395 raise error.Abort('Expansion not yet supported on widen/narrow')
396 396
397 397 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
398 398 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
399 399 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
400 400 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
401 401 widening = addedincludes or removedexcludes
402 402 narrowing = removedincludes or addedexcludes
403 403 only_show = not widening and not narrowing
404 404
405 405 # Only print the current narrowspec.
406 406 if only_show:
407 407 include, exclude = repo.narrowpats
408 408
409 409 ui.pager('tracked')
410 410 fm = ui.formatter('narrow', opts)
411 411 for i in sorted(include):
412 412 fm.startitem()
413 413 fm.write('status', '%s ', 'I', label='narrow.included')
414 414 fm.write('pat', '%s\n', i, label='narrow.included')
415 415 for i in sorted(exclude):
416 416 fm.startitem()
417 417 fm.write('status', '%s ', 'X', label='narrow.excluded')
418 418 fm.write('pat', '%s\n', i, label='narrow.excluded')
419 419 fm.end()
420 420 return 0
421 421
422 422 with repo.wlock(), repo.lock():
423 423 cmdutil.bailifchanged(repo)
424 424
425 425 # Find the revisions we have in common with the remote. These will
426 426 # be used for finding local-only changes for narrowing. They will
427 427 # also define the set of revisions to update for widening.
428 428 remotepath = ui.expandpath(remotepath or 'default')
429 429 url, branches = hg.parseurl(remotepath)
430 430 ui.status(_('comparing with %s\n') % util.hidepassword(url))
431 431 remote = hg.peer(repo, opts, url)
432 432 commoninc = discovery.findcommonincoming(repo, remote)
433 433
434 434 oldincludes, oldexcludes = repo.narrowpats
435 435 if narrowing:
436 436 newincludes = oldincludes - removedincludes
437 437 newexcludes = oldexcludes | addedexcludes
438 438 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
439 439 newincludes, newexcludes,
440 440 opts['force_delete_local_changes'])
441 441 # _narrow() updated the narrowspec and _widen() below needs to
442 442 # use the updated values as its base (otherwise removed includes
443 443 # and addedexcludes will be lost in the resulting narrowspec)
444 444 oldincludes = newincludes
445 445 oldexcludes = newexcludes
446 446
447 447 if widening:
448 448 newincludes = oldincludes | addedincludes
449 449 newexcludes = oldexcludes - removedexcludes
450 450 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
451 451
452 452 return 0
@@ -1,36 +1,36 b''
1 1 # narrowrepo.py - repository which supports narrow revlogs, lazy loading
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 from . import (
11 narrowbundle2,
12 11 narrowdirstate,
13 12 narrowrevlog,
13 narrowwirepeer,
14 14 )
15 15
16 16 def wraprepo(repo):
17 17 """Enables narrow clone functionality on a single local repository."""
18 18
19 19 class narrowrepository(repo.__class__):
20 20
21 21 def file(self, f):
22 22 fl = super(narrowrepository, self).file(f)
23 23 narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
24 24 return fl
25 25
26 26 def _makedirstate(self):
27 27 dirstate = super(narrowrepository, self)._makedirstate()
28 28 return narrowdirstate.wrapdirstate(self, dirstate)
29 29
30 30 def peer(self):
31 31 peer = super(narrowrepository, self).peer()
32 peer._caps.add(narrowbundle2.NARROWCAP)
33 peer._caps.add(narrowbundle2.ELLIPSESCAP)
32 peer._caps.add(narrowwirepeer.NARROWCAP)
33 peer._caps.add(narrowwirepeer.ELLIPSESCAP)
34 34 return peer
35 35
36 36 repo.__class__ = narrowrepository
@@ -1,65 +1,66 b''
1 1 # narrowwirepeer.py - passes narrow spec with unbundle command
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 from mercurial.i18n import _
11 11 from mercurial import (
12 12 error,
13 13 extensions,
14 14 hg,
15 15 narrowspec,
16 16 node,
17 17 wireprotov1server,
18 18 )
19 19
20 from . import narrowbundle2
20 NARROWCAP = 'narrow'
21 ELLIPSESCAP = 'ellipses'
21 22
22 23 def uisetup():
23 24 def peersetup(ui, peer):
24 25 # We must set up the expansion before reposetup below, since it's used
25 26 # at clone time before we have a repo.
26 27 class expandingpeer(peer.__class__):
27 28 def expandnarrow(self, narrow_include, narrow_exclude, nodes):
28 29 ui.status(_("expanding narrowspec\n"))
29 30 if not self.capable('exp-expandnarrow'):
30 31 raise error.Abort(
31 32 'peer does not support expanding narrowspecs')
32 33
33 34 hex_nodes = (node.hex(n) for n in nodes)
34 35 new_narrowspec = self._call(
35 36 'expandnarrow',
36 37 includepats=','.join(narrow_include),
37 38 excludepats=','.join(narrow_exclude),
38 39 nodes=','.join(hex_nodes))
39 40
40 41 return narrowspec.parseserverpatterns(new_narrowspec)
41 42 peer.__class__ = expandingpeer
42 43 hg.wirepeersetupfuncs.append(peersetup)
43 44
44 45 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
45 46
46 47 def addnarrowcap(orig, repo, proto):
47 48 """add the narrow capability to the server"""
48 49 caps = orig(repo, proto)
49 caps.append(narrowbundle2.NARROWCAP)
50 caps.append(NARROWCAP)
50 51 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
51 caps.append(narrowbundle2.ELLIPSESCAP)
52 caps.append(ELLIPSESCAP)
52 53 return caps
53 54
54 55 def reposetup(repo):
55 56 def wirereposetup(ui, peer):
56 57 def wrapped(orig, cmd, *args, **kwargs):
57 58 if cmd == 'unbundle':
58 59 # TODO: don't blindly add include/exclude wireproto
59 60 # arguments to unbundle.
60 61 include, exclude = repo.narrowpats
61 62 kwargs[r"includepats"] = ','.join(include)
62 63 kwargs[r"excludepats"] = ','.join(exclude)
63 64 return orig(cmd, *args, **kwargs)
64 65 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
65 66 hg.wirepeersetupfuncs.append(wirereposetup)
General Comments 0
You need to be logged in to leave comments. Login now