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