##// END OF EJS Templates
changegroup: move file matcher from narrow extension...
Gregory Szorc -
r38830:1d01cf04 default
parent child Browse files
Show More
@@ -1,333 +1,327 b''
1 # narrowbundle2.py - bundle2 extensions for narrow repository support
1 # narrowbundle2.py - bundle2 extensions for narrow repository support
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import struct
11 import struct
12
12
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 from mercurial.node import (
14 from mercurial.node import (
15 bin,
15 bin,
16 nullid,
16 nullid,
17 )
17 )
18 from mercurial import (
18 from mercurial import (
19 bundle2,
19 bundle2,
20 changegroup,
20 changegroup,
21 error,
21 error,
22 exchange,
22 exchange,
23 extensions,
23 extensions,
24 narrowspec,
24 narrowspec,
25 repair,
25 repair,
26 util,
26 util,
27 wireprototypes,
27 wireprototypes,
28 )
28 )
29 from mercurial.utils import (
29 from mercurial.utils import (
30 stringutil,
30 stringutil,
31 )
31 )
32
32
33 NARROWCAP = 'narrow'
33 NARROWCAP = 'narrow'
34 _NARROWACL_SECTION = 'narrowhgacl'
34 _NARROWACL_SECTION = 'narrowhgacl'
35 _CHANGESPECPART = NARROWCAP + ':changespec'
35 _CHANGESPECPART = NARROWCAP + ':changespec'
36 _SPECPART = NARROWCAP + ':spec'
36 _SPECPART = NARROWCAP + ':spec'
37 _SPECPART_INCLUDE = 'include'
37 _SPECPART_INCLUDE = 'include'
38 _SPECPART_EXCLUDE = 'exclude'
38 _SPECPART_EXCLUDE = 'exclude'
39 _KILLNODESIGNAL = 'KILL'
39 _KILLNODESIGNAL = 'KILL'
40 _DONESIGNAL = 'DONE'
40 _DONESIGNAL = 'DONE'
41 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
41 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
42 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
42 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
43 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
43 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
44 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
44 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
45
45
46 # When advertising capabilities, always include narrow clone support.
46 # When advertising capabilities, always include narrow clone support.
47 def getrepocaps_narrow(orig, repo, **kwargs):
47 def getrepocaps_narrow(orig, repo, **kwargs):
48 caps = orig(repo, **kwargs)
48 caps = orig(repo, **kwargs)
49 caps[NARROWCAP] = ['v0']
49 caps[NARROWCAP] = ['v0']
50 return caps
50 return caps
51
51
52 def _packellipsischangegroup(repo, common, match, relevant_nodes,
52 def _packellipsischangegroup(repo, common, match, relevant_nodes,
53 ellipsisroots, visitnodes, depth, source, version):
53 ellipsisroots, visitnodes, depth, source, version):
54 if version in ('01', '02'):
54 if version in ('01', '02'):
55 raise error.Abort(
55 raise error.Abort(
56 'ellipsis nodes require at least cg3 on client and server, '
56 'ellipsis nodes require at least cg3 on client and server, '
57 'but negotiated version %s' % version)
57 'but negotiated version %s' % version)
58 # We wrap cg1packer.revchunk, using a side channel to pass
58 # We wrap cg1packer.revchunk, using a side channel to pass
59 # relevant_nodes into that area. Then if linknode isn't in the
59 # relevant_nodes into that area. Then if linknode isn't in the
60 # set, we know we have an ellipsis node and we should defer
60 # set, we know we have an ellipsis node and we should defer
61 # sending that node's data. We override close() to detect
61 # sending that node's data. We override close() to detect
62 # pending ellipsis nodes and flush them.
62 # pending ellipsis nodes and flush them.
63 packer = changegroup.getbundler(version, repo)
63 packer = changegroup.getbundler(version, repo,
64 # Let the packer have access to the narrow matcher so it can
64 filematcher=match)
65 # omit filelogs and dirlogs as needed
66 packer._narrow_matcher = lambda : match
67 # Give the packer the list of nodes which should not be
65 # Give the packer the list of nodes which should not be
68 # ellipsis nodes. We store this rather than the set of nodes
66 # ellipsis nodes. We store this rather than the set of nodes
69 # that should be an ellipsis because for very large histories
67 # that should be an ellipsis because for very large histories
70 # we expect this to be significantly smaller.
68 # we expect this to be significantly smaller.
71 packer.full_nodes = relevant_nodes
69 packer.full_nodes = relevant_nodes
72 # Maps ellipsis revs to their roots at the changelog level.
70 # Maps ellipsis revs to their roots at the changelog level.
73 packer.precomputed_ellipsis = ellipsisroots
71 packer.precomputed_ellipsis = ellipsisroots
74 # Maps CL revs to per-revlog revisions. Cleared in close() at
72 # Maps CL revs to per-revlog revisions. Cleared in close() at
75 # the end of each group.
73 # the end of each group.
76 packer.clrev_to_localrev = {}
74 packer.clrev_to_localrev = {}
77 packer.next_clrev_to_localrev = {}
75 packer.next_clrev_to_localrev = {}
78 # Maps changelog nodes to changelog revs. Filled in once
76 # Maps changelog nodes to changelog revs. Filled in once
79 # during changelog stage and then left unmodified.
77 # during changelog stage and then left unmodified.
80 packer.clnode_to_rev = {}
78 packer.clnode_to_rev = {}
81 packer.changelog_done = False
79 packer.changelog_done = False
82 # If true, informs the packer that it is serving shallow content and might
80 # If true, informs the packer that it is serving shallow content and might
83 # need to pack file contents not introduced by the changes being packed.
81 # need to pack file contents not introduced by the changes being packed.
84 packer.is_shallow = depth is not None
82 packer.is_shallow = depth is not None
85
83
86 return packer.generate(common, visitnodes, False, source)
84 return packer.generate(common, visitnodes, False, source)
87
85
88 # Serve a changegroup for a client with a narrow clone.
86 # Serve a changegroup for a client with a narrow clone.
89 def getbundlechangegrouppart_narrow(bundler, repo, source,
87 def getbundlechangegrouppart_narrow(bundler, repo, source,
90 bundlecaps=None, b2caps=None, heads=None,
88 bundlecaps=None, b2caps=None, heads=None,
91 common=None, **kwargs):
89 common=None, **kwargs):
92 cgversions = b2caps.get('changegroup')
90 cgversions = b2caps.get('changegroup')
93 if cgversions: # 3.1 and 3.2 ship with an empty value
91 if cgversions: # 3.1 and 3.2 ship with an empty value
94 cgversions = [v for v in cgversions
92 cgversions = [v for v in cgversions
95 if v in changegroup.supportedoutgoingversions(repo)]
93 if v in changegroup.supportedoutgoingversions(repo)]
96 if not cgversions:
94 if not cgversions:
97 raise ValueError(_('no common changegroup version'))
95 raise ValueError(_('no common changegroup version'))
98 version = max(cgversions)
96 version = max(cgversions)
99 else:
97 else:
100 raise ValueError(_("server does not advertise changegroup version,"
98 raise ValueError(_("server does not advertise changegroup version,"
101 " can't negotiate support for ellipsis nodes"))
99 " can't negotiate support for ellipsis nodes"))
102
100
103 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
101 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
104 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
102 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
105 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
103 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
106 if not repo.ui.configbool("experimental", "narrowservebrokenellipses"):
104 if not repo.ui.configbool("experimental", "narrowservebrokenellipses"):
107 outgoing = exchange._computeoutgoing(repo, heads, common)
105 outgoing = exchange._computeoutgoing(repo, heads, common)
108 if not outgoing.missing:
106 if not outgoing.missing:
109 return
107 return
110 def wrappedgetbundler(orig, *args, **kwargs):
108
111 bundler = orig(*args, **kwargs)
109 cg = changegroup.makestream(repo, outgoing, version, source,
112 bundler._narrow_matcher = lambda : newmatch
110 filematcher=newmatch)
113 return bundler
114 with extensions.wrappedfunction(changegroup, 'getbundler',
115 wrappedgetbundler):
116 cg = changegroup.makestream(repo, outgoing, version, source)
117 part = bundler.newpart('changegroup', data=cg)
111 part = bundler.newpart('changegroup', data=cg)
118 part.addparam('version', version)
112 part.addparam('version', version)
119 if 'treemanifest' in repo.requirements:
113 if 'treemanifest' in repo.requirements:
120 part.addparam('treemanifest', '1')
114 part.addparam('treemanifest', '1')
121
115
122 if include or exclude:
116 if include or exclude:
123 narrowspecpart = bundler.newpart(_SPECPART)
117 narrowspecpart = bundler.newpart(_SPECPART)
124 if include:
118 if include:
125 narrowspecpart.addparam(
119 narrowspecpart.addparam(
126 _SPECPART_INCLUDE, '\n'.join(include), mandatory=True)
120 _SPECPART_INCLUDE, '\n'.join(include), mandatory=True)
127 if exclude:
121 if exclude:
128 narrowspecpart.addparam(
122 narrowspecpart.addparam(
129 _SPECPART_EXCLUDE, '\n'.join(exclude), mandatory=True)
123 _SPECPART_EXCLUDE, '\n'.join(exclude), mandatory=True)
130
124
131 return
125 return
132
126
133 depth = kwargs.get(r'depth', None)
127 depth = kwargs.get(r'depth', None)
134 if depth is not None:
128 if depth is not None:
135 depth = int(depth)
129 depth = int(depth)
136 if depth < 1:
130 if depth < 1:
137 raise error.Abort(_('depth must be positive, got %d') % depth)
131 raise error.Abort(_('depth must be positive, got %d') % depth)
138
132
139 heads = set(heads or repo.heads())
133 heads = set(heads or repo.heads())
140 common = set(common or [nullid])
134 common = set(common or [nullid])
141 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
135 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
142 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
136 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
143 known = {bin(n) for n in kwargs.get(r'known', [])}
137 known = {bin(n) for n in kwargs.get(r'known', [])}
144 if known and (oldinclude != include or oldexclude != exclude):
138 if known and (oldinclude != include or oldexclude != exclude):
145 # Steps:
139 # Steps:
146 # 1. Send kill for "$known & ::common"
140 # 1. Send kill for "$known & ::common"
147 #
141 #
148 # 2. Send changegroup for ::common
142 # 2. Send changegroup for ::common
149 #
143 #
150 # 3. Proceed.
144 # 3. Proceed.
151 #
145 #
152 # In the future, we can send kills for only the specific
146 # In the future, we can send kills for only the specific
153 # nodes we know should go away or change shape, and then
147 # nodes we know should go away or change shape, and then
154 # send a data stream that tells the client something like this:
148 # send a data stream that tells the client something like this:
155 #
149 #
156 # a) apply this changegroup
150 # a) apply this changegroup
157 # b) apply nodes XXX, YYY, ZZZ that you already have
151 # b) apply nodes XXX, YYY, ZZZ that you already have
158 # c) goto a
152 # c) goto a
159 #
153 #
160 # until they've built up the full new state.
154 # until they've built up the full new state.
161 # Convert to revnums and intersect with "common". The client should
155 # Convert to revnums and intersect with "common". The client should
162 # have made it a subset of "common" already, but let's be safe.
156 # have made it a subset of "common" already, but let's be safe.
163 known = set(repo.revs("%ln & ::%ln", known, common))
157 known = set(repo.revs("%ln & ::%ln", known, common))
164 # TODO: we could send only roots() of this set, and the
158 # TODO: we could send only roots() of this set, and the
165 # list of nodes in common, and the client could work out
159 # list of nodes in common, and the client could work out
166 # what to strip, instead of us explicitly sending every
160 # what to strip, instead of us explicitly sending every
167 # single node.
161 # single node.
168 deadrevs = known
162 deadrevs = known
169 def genkills():
163 def genkills():
170 for r in deadrevs:
164 for r in deadrevs:
171 yield _KILLNODESIGNAL
165 yield _KILLNODESIGNAL
172 yield repo.changelog.node(r)
166 yield repo.changelog.node(r)
173 yield _DONESIGNAL
167 yield _DONESIGNAL
174 bundler.newpart(_CHANGESPECPART, data=genkills())
168 bundler.newpart(_CHANGESPECPART, data=genkills())
175 newvisit, newfull, newellipsis = exchange._computeellipsis(
169 newvisit, newfull, newellipsis = exchange._computeellipsis(
176 repo, set(), common, known, newmatch)
170 repo, set(), common, known, newmatch)
177 if newvisit:
171 if newvisit:
178 cg = _packellipsischangegroup(
172 cg = _packellipsischangegroup(
179 repo, common, newmatch, newfull, newellipsis,
173 repo, common, newmatch, newfull, newellipsis,
180 newvisit, depth, source, version)
174 newvisit, depth, source, version)
181 part = bundler.newpart('changegroup', data=cg)
175 part = bundler.newpart('changegroup', data=cg)
182 part.addparam('version', version)
176 part.addparam('version', version)
183 if 'treemanifest' in repo.requirements:
177 if 'treemanifest' in repo.requirements:
184 part.addparam('treemanifest', '1')
178 part.addparam('treemanifest', '1')
185
179
186 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
180 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
187 repo, common, heads, set(), newmatch, depth=depth)
181 repo, common, heads, set(), newmatch, depth=depth)
188
182
189 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
183 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
190 if visitnodes:
184 if visitnodes:
191 cg = _packellipsischangegroup(
185 cg = _packellipsischangegroup(
192 repo, common, newmatch, relevant_nodes, ellipsisroots,
186 repo, common, newmatch, relevant_nodes, ellipsisroots,
193 visitnodes, depth, source, version)
187 visitnodes, depth, source, version)
194 part = bundler.newpart('changegroup', data=cg)
188 part = bundler.newpart('changegroup', data=cg)
195 part.addparam('version', version)
189 part.addparam('version', version)
196 if 'treemanifest' in repo.requirements:
190 if 'treemanifest' in repo.requirements:
197 part.addparam('treemanifest', '1')
191 part.addparam('treemanifest', '1')
198
192
199 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
193 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
200 def _handlechangespec_2(op, inpart):
194 def _handlechangespec_2(op, inpart):
201 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
195 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
202 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
196 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
203 if not changegroup.NARROW_REQUIREMENT in op.repo.requirements:
197 if not changegroup.NARROW_REQUIREMENT in op.repo.requirements:
204 op.repo.requirements.add(changegroup.NARROW_REQUIREMENT)
198 op.repo.requirements.add(changegroup.NARROW_REQUIREMENT)
205 op.repo._writerequirements()
199 op.repo._writerequirements()
206 op.repo.setnarrowpats(includepats, excludepats)
200 op.repo.setnarrowpats(includepats, excludepats)
207
201
208 @bundle2.parthandler(_CHANGESPECPART)
202 @bundle2.parthandler(_CHANGESPECPART)
209 def _handlechangespec(op, inpart):
203 def _handlechangespec(op, inpart):
210 repo = op.repo
204 repo = op.repo
211 cl = repo.changelog
205 cl = repo.changelog
212
206
213 # changesets which need to be stripped entirely. either they're no longer
207 # changesets which need to be stripped entirely. either they're no longer
214 # needed in the new narrow spec, or the server is sending a replacement
208 # needed in the new narrow spec, or the server is sending a replacement
215 # in the changegroup part.
209 # in the changegroup part.
216 clkills = set()
210 clkills = set()
217
211
218 # A changespec part contains all the updates to ellipsis nodes
212 # A changespec part contains all the updates to ellipsis nodes
219 # that will happen as a result of widening or narrowing a
213 # that will happen as a result of widening or narrowing a
220 # repo. All the changes that this block encounters are ellipsis
214 # repo. All the changes that this block encounters are ellipsis
221 # nodes or flags to kill an existing ellipsis.
215 # nodes or flags to kill an existing ellipsis.
222 chunksignal = changegroup.readexactly(inpart, 4)
216 chunksignal = changegroup.readexactly(inpart, 4)
223 while chunksignal != _DONESIGNAL:
217 while chunksignal != _DONESIGNAL:
224 if chunksignal == _KILLNODESIGNAL:
218 if chunksignal == _KILLNODESIGNAL:
225 # a node used to be an ellipsis but isn't anymore
219 # a node used to be an ellipsis but isn't anymore
226 ck = changegroup.readexactly(inpart, 20)
220 ck = changegroup.readexactly(inpart, 20)
227 if cl.hasnode(ck):
221 if cl.hasnode(ck):
228 clkills.add(ck)
222 clkills.add(ck)
229 else:
223 else:
230 raise error.Abort(
224 raise error.Abort(
231 _('unexpected changespec node chunk type: %s') % chunksignal)
225 _('unexpected changespec node chunk type: %s') % chunksignal)
232 chunksignal = changegroup.readexactly(inpart, 4)
226 chunksignal = changegroup.readexactly(inpart, 4)
233
227
234 if clkills:
228 if clkills:
235 # preserve bookmarks that repair.strip() would otherwise strip
229 # preserve bookmarks that repair.strip() would otherwise strip
236 bmstore = repo._bookmarks
230 bmstore = repo._bookmarks
237 class dummybmstore(dict):
231 class dummybmstore(dict):
238 def applychanges(self, repo, tr, changes):
232 def applychanges(self, repo, tr, changes):
239 pass
233 pass
240 def recordchange(self, tr): # legacy version
234 def recordchange(self, tr): # legacy version
241 pass
235 pass
242 repo._bookmarks = dummybmstore()
236 repo._bookmarks = dummybmstore()
243 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
237 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
244 topic='widen')
238 topic='widen')
245 repo._bookmarks = bmstore
239 repo._bookmarks = bmstore
246 if chgrpfile:
240 if chgrpfile:
247 op._widen_uninterr = repo.ui.uninterruptable()
241 op._widen_uninterr = repo.ui.uninterruptable()
248 op._widen_uninterr.__enter__()
242 op._widen_uninterr.__enter__()
249 # presence of _widen_bundle attribute activates widen handler later
243 # presence of _widen_bundle attribute activates widen handler later
250 op._widen_bundle = chgrpfile
244 op._widen_bundle = chgrpfile
251 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
245 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
252 # will currently always be there when using the core+narrowhg server, but
246 # will currently always be there when using the core+narrowhg server, but
253 # other servers may include a changespec part even when not widening (e.g.
247 # other servers may include a changespec part even when not widening (e.g.
254 # because we're deepening a shallow repo).
248 # because we're deepening a shallow repo).
255 if util.safehasattr(repo, 'setnewnarrowpats'):
249 if util.safehasattr(repo, 'setnewnarrowpats'):
256 repo.setnewnarrowpats()
250 repo.setnewnarrowpats()
257
251
258 def handlechangegroup_widen(op, inpart):
252 def handlechangegroup_widen(op, inpart):
259 """Changegroup exchange handler which restores temporarily-stripped nodes"""
253 """Changegroup exchange handler which restores temporarily-stripped nodes"""
260 # We saved a bundle with stripped node data we must now restore.
254 # We saved a bundle with stripped node data we must now restore.
261 # This approach is based on mercurial/repair.py@6ee26a53c111.
255 # This approach is based on mercurial/repair.py@6ee26a53c111.
262 repo = op.repo
256 repo = op.repo
263 ui = op.ui
257 ui = op.ui
264
258
265 chgrpfile = op._widen_bundle
259 chgrpfile = op._widen_bundle
266 del op._widen_bundle
260 del op._widen_bundle
267 vfs = repo.vfs
261 vfs = repo.vfs
268
262
269 ui.note(_("adding branch\n"))
263 ui.note(_("adding branch\n"))
270 f = vfs.open(chgrpfile, "rb")
264 f = vfs.open(chgrpfile, "rb")
271 try:
265 try:
272 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
266 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
273 if not ui.verbose:
267 if not ui.verbose:
274 # silence internal shuffling chatter
268 # silence internal shuffling chatter
275 ui.pushbuffer()
269 ui.pushbuffer()
276 if isinstance(gen, bundle2.unbundle20):
270 if isinstance(gen, bundle2.unbundle20):
277 with repo.transaction('strip') as tr:
271 with repo.transaction('strip') as tr:
278 bundle2.processbundle(repo, gen, lambda: tr)
272 bundle2.processbundle(repo, gen, lambda: tr)
279 else:
273 else:
280 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
274 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
281 if not ui.verbose:
275 if not ui.verbose:
282 ui.popbuffer()
276 ui.popbuffer()
283 finally:
277 finally:
284 f.close()
278 f.close()
285
279
286 # remove undo files
280 # remove undo files
287 for undovfs, undofile in repo.undofiles():
281 for undovfs, undofile in repo.undofiles():
288 try:
282 try:
289 undovfs.unlink(undofile)
283 undovfs.unlink(undofile)
290 except OSError as e:
284 except OSError as e:
291 if e.errno != errno.ENOENT:
285 if e.errno != errno.ENOENT:
292 ui.warn(_('error removing %s: %s\n') %
286 ui.warn(_('error removing %s: %s\n') %
293 (undovfs.join(undofile), stringutil.forcebytestr(e)))
287 (undovfs.join(undofile), stringutil.forcebytestr(e)))
294
288
295 # Remove partial backup only if there were no exceptions
289 # Remove partial backup only if there were no exceptions
296 op._widen_uninterr.__exit__(None, None, None)
290 op._widen_uninterr.__exit__(None, None, None)
297 vfs.unlink(chgrpfile)
291 vfs.unlink(chgrpfile)
298
292
299 def setup():
293 def setup():
300 """Enable narrow repo support in bundle2-related extension points."""
294 """Enable narrow repo support in bundle2-related extension points."""
301 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
295 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
302
296
303 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
297 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
304
298
305 getbundleargs['narrow'] = 'boolean'
299 getbundleargs['narrow'] = 'boolean'
306 getbundleargs['depth'] = 'plain'
300 getbundleargs['depth'] = 'plain'
307 getbundleargs['oldincludepats'] = 'csv'
301 getbundleargs['oldincludepats'] = 'csv'
308 getbundleargs['oldexcludepats'] = 'csv'
302 getbundleargs['oldexcludepats'] = 'csv'
309 getbundleargs['includepats'] = 'csv'
303 getbundleargs['includepats'] = 'csv'
310 getbundleargs['excludepats'] = 'csv'
304 getbundleargs['excludepats'] = 'csv'
311 getbundleargs['known'] = 'csv'
305 getbundleargs['known'] = 'csv'
312
306
313 # Extend changegroup serving to handle requests from narrow clients.
307 # Extend changegroup serving to handle requests from narrow clients.
314 origcgfn = exchange.getbundle2partsmapping['changegroup']
308 origcgfn = exchange.getbundle2partsmapping['changegroup']
315 def wrappedcgfn(*args, **kwargs):
309 def wrappedcgfn(*args, **kwargs):
316 repo = args[1]
310 repo = args[1]
317 if repo.ui.has_section(_NARROWACL_SECTION):
311 if repo.ui.has_section(_NARROWACL_SECTION):
318 getbundlechangegrouppart_narrow(
312 getbundlechangegrouppart_narrow(
319 *args, **exchange.applynarrowacl(repo, kwargs))
313 *args, **exchange.applynarrowacl(repo, kwargs))
320 elif kwargs.get(r'narrow', False):
314 elif kwargs.get(r'narrow', False):
321 getbundlechangegrouppart_narrow(*args, **kwargs)
315 getbundlechangegrouppart_narrow(*args, **kwargs)
322 else:
316 else:
323 origcgfn(*args, **kwargs)
317 origcgfn(*args, **kwargs)
324 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
318 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
325
319
326 # Extend changegroup receiver so client can fixup after widen requests.
320 # Extend changegroup receiver so client can fixup after widen requests.
327 origcghandler = bundle2.parthandlermapping['changegroup']
321 origcghandler = bundle2.parthandlermapping['changegroup']
328 def wrappedcghandler(op, inpart):
322 def wrappedcghandler(op, inpart):
329 origcghandler(op, inpart)
323 origcghandler(op, inpart)
330 if util.safehasattr(op, '_widen_bundle'):
324 if util.safehasattr(op, '_widen_bundle'):
331 handlechangegroup_widen(op, inpart)
325 handlechangegroup_widen(op, inpart)
332 wrappedcghandler.params = origcghandler.params
326 wrappedcghandler.params = origcghandler.params
333 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
327 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
@@ -1,374 +1,362 b''
1 # narrowchangegroup.py - narrow clone changegroup creation and consumption
1 # narrowchangegroup.py - narrow clone changegroup creation and consumption
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import (
11 from mercurial import (
12 changegroup,
12 changegroup,
13 error,
13 error,
14 extensions,
14 extensions,
15 manifest,
15 manifest,
16 match as matchmod,
17 mdiff,
16 mdiff,
18 node,
17 node,
19 pycompat,
18 pycompat,
20 revlog,
19 revlog,
21 util,
20 util,
22 )
21 )
23
22
24 def setup():
23 def setup():
25
26 def _cgmatcher(cgpacker):
27 localmatcher = cgpacker._repo.narrowmatch()
28 remotematcher = getattr(cgpacker, '_narrow_matcher', lambda: None)()
29 if remotematcher:
30 return matchmod.intersectmatchers(localmatcher, remotematcher)
31 else:
32 return localmatcher
33
34 def prune(orig, self, revlog, missing, commonrevs):
24 def prune(orig, self, revlog, missing, commonrevs):
35 if isinstance(revlog, manifest.manifestrevlog):
25 if isinstance(revlog, manifest.manifestrevlog):
36 matcher = _cgmatcher(self)
26 if not self._filematcher.visitdir(revlog._dir[:-1] or '.'):
37 if (matcher and
38 not matcher.visitdir(revlog._dir[:-1] or '.')):
39 return []
27 return []
28
40 return orig(self, revlog, missing, commonrevs)
29 return orig(self, revlog, missing, commonrevs)
41
30
42 extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
31 extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
43
32
44 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
33 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
45 source):
34 source):
46 matcher = _cgmatcher(self)
35 changedfiles = list(filter(self._filematcher, changedfiles))
47 if matcher:
36
48 changedfiles = list(filter(matcher, changedfiles))
49 if getattr(self, 'is_shallow', False):
37 if getattr(self, 'is_shallow', False):
50 # See comment in generate() for why this sadness is a thing.
38 # See comment in generate() for why this sadness is a thing.
51 mfdicts = self._mfdicts
39 mfdicts = self._mfdicts
52 del self._mfdicts
40 del self._mfdicts
53 # In a shallow clone, the linknodes callback needs to also include
41 # In a shallow clone, the linknodes callback needs to also include
54 # those file nodes that are in the manifests we sent but weren't
42 # those file nodes that are in the manifests we sent but weren't
55 # introduced by those manifests.
43 # introduced by those manifests.
56 commonctxs = [self._repo[c] for c in commonrevs]
44 commonctxs = [self._repo[c] for c in commonrevs]
57 oldlinknodes = linknodes
45 oldlinknodes = linknodes
58 clrev = self._repo.changelog.rev
46 clrev = self._repo.changelog.rev
59 def linknodes(flog, fname):
47 def linknodes(flog, fname):
60 for c in commonctxs:
48 for c in commonctxs:
61 try:
49 try:
62 fnode = c.filenode(fname)
50 fnode = c.filenode(fname)
63 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
51 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
64 except error.ManifestLookupError:
52 except error.ManifestLookupError:
65 pass
53 pass
66 links = oldlinknodes(flog, fname)
54 links = oldlinknodes(flog, fname)
67 if len(links) != len(mfdicts):
55 if len(links) != len(mfdicts):
68 for mf, lr in mfdicts:
56 for mf, lr in mfdicts:
69 fnode = mf.get(fname, None)
57 fnode = mf.get(fname, None)
70 if fnode in links:
58 if fnode in links:
71 links[fnode] = min(links[fnode], lr, key=clrev)
59 links[fnode] = min(links[fnode], lr, key=clrev)
72 elif fnode:
60 elif fnode:
73 links[fnode] = lr
61 links[fnode] = lr
74 return links
62 return links
75 return orig(self, changedfiles, linknodes, commonrevs, source)
63 return orig(self, changedfiles, linknodes, commonrevs, source)
76 extensions.wrapfunction(
64 extensions.wrapfunction(
77 changegroup.cg1packer, 'generatefiles', generatefiles)
65 changegroup.cg1packer, 'generatefiles', generatefiles)
78
66
79 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
67 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
80 n = revlog_.node(rev)
68 n = revlog_.node(rev)
81 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
69 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
82 flags = revlog_.flags(rev)
70 flags = revlog_.flags(rev)
83 flags |= revlog.REVIDX_ELLIPSIS
71 flags |= revlog.REVIDX_ELLIPSIS
84 meta = packer.builddeltaheader(
72 meta = packer.builddeltaheader(
85 n, p1n, p2n, node.nullid, linknode, flags)
73 n, p1n, p2n, node.nullid, linknode, flags)
86 # TODO: try and actually send deltas for ellipsis data blocks
74 # TODO: try and actually send deltas for ellipsis data blocks
87 diffheader = mdiff.trivialdiffheader(len(data))
75 diffheader = mdiff.trivialdiffheader(len(data))
88 l = len(meta) + len(diffheader) + len(data)
76 l = len(meta) + len(diffheader) + len(data)
89 return ''.join((changegroup.chunkheader(l),
77 return ''.join((changegroup.chunkheader(l),
90 meta,
78 meta,
91 diffheader,
79 diffheader,
92 data))
80 data))
93
81
94 def close(orig, self):
82 def close(orig, self):
95 getattr(self, 'clrev_to_localrev', {}).clear()
83 getattr(self, 'clrev_to_localrev', {}).clear()
96 if getattr(self, 'next_clrev_to_localrev', {}):
84 if getattr(self, 'next_clrev_to_localrev', {}):
97 self.clrev_to_localrev = self.next_clrev_to_localrev
85 self.clrev_to_localrev = self.next_clrev_to_localrev
98 del self.next_clrev_to_localrev
86 del self.next_clrev_to_localrev
99 self.changelog_done = True
87 self.changelog_done = True
100 return orig(self)
88 return orig(self)
101 extensions.wrapfunction(changegroup.cg1packer, 'close', close)
89 extensions.wrapfunction(changegroup.cg1packer, 'close', close)
102
90
103 # In a perfect world, we'd generate better ellipsis-ified graphs
91 # In a perfect world, we'd generate better ellipsis-ified graphs
104 # for non-changelog revlogs. In practice, we haven't started doing
92 # for non-changelog revlogs. In practice, we haven't started doing
105 # that yet, so the resulting DAGs for the manifestlog and filelogs
93 # that yet, so the resulting DAGs for the manifestlog and filelogs
106 # are actually full of bogus parentage on all the ellipsis
94 # are actually full of bogus parentage on all the ellipsis
107 # nodes. This has the side effect that, while the contents are
95 # nodes. This has the side effect that, while the contents are
108 # correct, the individual DAGs might be completely out of whack in
96 # correct, the individual DAGs might be completely out of whack in
109 # a case like 882681bc3166 and its ancestors (back about 10
97 # a case like 882681bc3166 and its ancestors (back about 10
110 # revisions or so) in the main hg repo.
98 # revisions or so) in the main hg repo.
111 #
99 #
112 # The one invariant we *know* holds is that the new (potentially
100 # The one invariant we *know* holds is that the new (potentially
113 # bogus) DAG shape will be valid if we order the nodes in the
101 # bogus) DAG shape will be valid if we order the nodes in the
114 # order that they're introduced in dramatis personae by the
102 # order that they're introduced in dramatis personae by the
115 # changelog, so what we do is we sort the non-changelog histories
103 # changelog, so what we do is we sort the non-changelog histories
116 # by the order in which they are used by the changelog.
104 # by the order in which they are used by the changelog.
117 def _sortgroup(orig, self, revlog, nodelist, lookup):
105 def _sortgroup(orig, self, revlog, nodelist, lookup):
118 if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
106 if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
119 return orig(self, revlog, nodelist, lookup)
107 return orig(self, revlog, nodelist, lookup)
120 key = lambda n: self.clnode_to_rev[lookup(n)]
108 key = lambda n: self.clnode_to_rev[lookup(n)]
121 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
109 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
122
110
123 extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
111 extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
124
112
125 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
113 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
126 '''yield a sequence of changegroup chunks (strings)'''
114 '''yield a sequence of changegroup chunks (strings)'''
127 # Note: other than delegating to orig, the only deviation in
115 # Note: other than delegating to orig, the only deviation in
128 # logic from normal hg's generate is marked with BEGIN/END
116 # logic from normal hg's generate is marked with BEGIN/END
129 # NARROW HACK.
117 # NARROW HACK.
130 if not util.safehasattr(self, 'full_nodes'):
118 if not util.safehasattr(self, 'full_nodes'):
131 # not sending a narrow bundle
119 # not sending a narrow bundle
132 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
120 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
133 yield x
121 yield x
134 return
122 return
135
123
136 repo = self._repo
124 repo = self._repo
137 cl = repo.changelog
125 cl = repo.changelog
138 mfl = repo.manifestlog
126 mfl = repo.manifestlog
139 mfrevlog = mfl._revlog
127 mfrevlog = mfl._revlog
140
128
141 clrevorder = {}
129 clrevorder = {}
142 mfs = {} # needed manifests
130 mfs = {} # needed manifests
143 fnodes = {} # needed file nodes
131 fnodes = {} # needed file nodes
144 changedfiles = set()
132 changedfiles = set()
145
133
146 # Callback for the changelog, used to collect changed files and manifest
134 # Callback for the changelog, used to collect changed files and manifest
147 # nodes.
135 # nodes.
148 # Returns the linkrev node (identity in the changelog case).
136 # Returns the linkrev node (identity in the changelog case).
149 def lookupcl(x):
137 def lookupcl(x):
150 c = cl.read(x)
138 c = cl.read(x)
151 clrevorder[x] = len(clrevorder)
139 clrevorder[x] = len(clrevorder)
152 # BEGIN NARROW HACK
140 # BEGIN NARROW HACK
153 #
141 #
154 # Only update mfs if x is going to be sent. Otherwise we
142 # Only update mfs if x is going to be sent. Otherwise we
155 # end up with bogus linkrevs specified for manifests and
143 # end up with bogus linkrevs specified for manifests and
156 # we skip some manifest nodes that we should otherwise
144 # we skip some manifest nodes that we should otherwise
157 # have sent.
145 # have sent.
158 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
146 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
159 n = c[0]
147 n = c[0]
160 # record the first changeset introducing this manifest version
148 # record the first changeset introducing this manifest version
161 mfs.setdefault(n, x)
149 mfs.setdefault(n, x)
162 # Set this narrow-specific dict so we have the lowest manifest
150 # Set this narrow-specific dict so we have the lowest manifest
163 # revnum to look up for this cl revnum. (Part of mapping
151 # revnum to look up for this cl revnum. (Part of mapping
164 # changelog ellipsis parents to manifest ellipsis parents)
152 # changelog ellipsis parents to manifest ellipsis parents)
165 self.next_clrev_to_localrev.setdefault(cl.rev(x),
153 self.next_clrev_to_localrev.setdefault(cl.rev(x),
166 mfrevlog.rev(n))
154 mfrevlog.rev(n))
167 # We can't trust the changed files list in the changeset if the
155 # We can't trust the changed files list in the changeset if the
168 # client requested a shallow clone.
156 # client requested a shallow clone.
169 if self.is_shallow:
157 if self.is_shallow:
170 changedfiles.update(mfl[c[0]].read().keys())
158 changedfiles.update(mfl[c[0]].read().keys())
171 else:
159 else:
172 changedfiles.update(c[3])
160 changedfiles.update(c[3])
173 # END NARROW HACK
161 # END NARROW HACK
174 # Record a complete list of potentially-changed files in
162 # Record a complete list of potentially-changed files in
175 # this manifest.
163 # this manifest.
176 return x
164 return x
177
165
178 self._verbosenote(_('uncompressed size of bundle content:\n'))
166 self._verbosenote(_('uncompressed size of bundle content:\n'))
179 size = 0
167 size = 0
180 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
168 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
181 size += len(chunk)
169 size += len(chunk)
182 yield chunk
170 yield chunk
183 self._verbosenote(_('%8.i (changelog)\n') % size)
171 self._verbosenote(_('%8.i (changelog)\n') % size)
184
172
185 # We need to make sure that the linkrev in the changegroup refers to
173 # We need to make sure that the linkrev in the changegroup refers to
186 # the first changeset that introduced the manifest or file revision.
174 # the first changeset that introduced the manifest or file revision.
187 # The fastpath is usually safer than the slowpath, because the filelogs
175 # The fastpath is usually safer than the slowpath, because the filelogs
188 # are walked in revlog order.
176 # are walked in revlog order.
189 #
177 #
190 # When taking the slowpath with reorder=None and the manifest revlog
178 # When taking the slowpath with reorder=None and the manifest revlog
191 # uses generaldelta, the manifest may be walked in the "wrong" order.
179 # uses generaldelta, the manifest may be walked in the "wrong" order.
192 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
180 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
193 # cc0ff93d0c0c).
181 # cc0ff93d0c0c).
194 #
182 #
195 # When taking the fastpath, we are only vulnerable to reordering
183 # When taking the fastpath, we are only vulnerable to reordering
196 # of the changelog itself. The changelog never uses generaldelta, so
184 # of the changelog itself. The changelog never uses generaldelta, so
197 # it is only reordered when reorder=True. To handle this case, we
185 # it is only reordered when reorder=True. To handle this case, we
198 # simply take the slowpath, which already has the 'clrevorder' logic.
186 # simply take the slowpath, which already has the 'clrevorder' logic.
199 # This was also fixed in cc0ff93d0c0c.
187 # This was also fixed in cc0ff93d0c0c.
200 fastpathlinkrev = fastpathlinkrev and not self._reorder
188 fastpathlinkrev = fastpathlinkrev and not self._reorder
201 # Treemanifests don't work correctly with fastpathlinkrev
189 # Treemanifests don't work correctly with fastpathlinkrev
202 # either, because we don't discover which directory nodes to
190 # either, because we don't discover which directory nodes to
203 # send along with files. This could probably be fixed.
191 # send along with files. This could probably be fixed.
204 fastpathlinkrev = fastpathlinkrev and (
192 fastpathlinkrev = fastpathlinkrev and (
205 'treemanifest' not in repo.requirements)
193 'treemanifest' not in repo.requirements)
206 # Shallow clones also don't work correctly with fastpathlinkrev
194 # Shallow clones also don't work correctly with fastpathlinkrev
207 # because file nodes may need to be sent for a manifest even if they
195 # because file nodes may need to be sent for a manifest even if they
208 # weren't introduced by that manifest.
196 # weren't introduced by that manifest.
209 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
197 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
210
198
211 for chunk in self.generatemanifests(commonrevs, clrevorder,
199 for chunk in self.generatemanifests(commonrevs, clrevorder,
212 fastpathlinkrev, mfs, fnodes, source):
200 fastpathlinkrev, mfs, fnodes, source):
213 yield chunk
201 yield chunk
214 # BEGIN NARROW HACK
202 # BEGIN NARROW HACK
215 mfdicts = None
203 mfdicts = None
216 if self.is_shallow:
204 if self.is_shallow:
217 mfdicts = [(self._repo.manifestlog[n].read(), lr)
205 mfdicts = [(self._repo.manifestlog[n].read(), lr)
218 for (n, lr) in mfs.iteritems()]
206 for (n, lr) in mfs.iteritems()]
219 # END NARROW HACK
207 # END NARROW HACK
220 mfs.clear()
208 mfs.clear()
221 clrevs = set(cl.rev(x) for x in clnodes)
209 clrevs = set(cl.rev(x) for x in clnodes)
222
210
223 if not fastpathlinkrev:
211 if not fastpathlinkrev:
224 def linknodes(unused, fname):
212 def linknodes(unused, fname):
225 return fnodes.get(fname, {})
213 return fnodes.get(fname, {})
226 else:
214 else:
227 cln = cl.node
215 cln = cl.node
228 def linknodes(filerevlog, fname):
216 def linknodes(filerevlog, fname):
229 llr = filerevlog.linkrev
217 llr = filerevlog.linkrev
230 fln = filerevlog.node
218 fln = filerevlog.node
231 revs = ((r, llr(r)) for r in filerevlog)
219 revs = ((r, llr(r)) for r in filerevlog)
232 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
220 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
233
221
234 # BEGIN NARROW HACK
222 # BEGIN NARROW HACK
235 #
223 #
236 # We need to pass the mfdicts variable down into
224 # We need to pass the mfdicts variable down into
237 # generatefiles(), but more than one command might have
225 # generatefiles(), but more than one command might have
238 # wrapped generatefiles so we can't modify the function
226 # wrapped generatefiles so we can't modify the function
239 # signature. Instead, we pass the data to ourselves using an
227 # signature. Instead, we pass the data to ourselves using an
240 # instance attribute. I'm sorry.
228 # instance attribute. I'm sorry.
241 self._mfdicts = mfdicts
229 self._mfdicts = mfdicts
242 # END NARROW HACK
230 # END NARROW HACK
243 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
231 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
244 source):
232 source):
245 yield chunk
233 yield chunk
246
234
247 yield self.close()
235 yield self.close()
248
236
249 if clnodes:
237 if clnodes:
250 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
238 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
251 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
239 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
252
240
253 def revchunk(orig, self, revlog, rev, prev, linknode):
241 def revchunk(orig, self, revlog, rev, prev, linknode):
254 if not util.safehasattr(self, 'full_nodes'):
242 if not util.safehasattr(self, 'full_nodes'):
255 # not sending a narrow changegroup
243 # not sending a narrow changegroup
256 for x in orig(self, revlog, rev, prev, linknode):
244 for x in orig(self, revlog, rev, prev, linknode):
257 yield x
245 yield x
258 return
246 return
259 # build up some mapping information that's useful later. See
247 # build up some mapping information that's useful later. See
260 # the local() nested function below.
248 # the local() nested function below.
261 if not self.changelog_done:
249 if not self.changelog_done:
262 self.clnode_to_rev[linknode] = rev
250 self.clnode_to_rev[linknode] = rev
263 linkrev = rev
251 linkrev = rev
264 self.clrev_to_localrev[linkrev] = rev
252 self.clrev_to_localrev[linkrev] = rev
265 else:
253 else:
266 linkrev = self.clnode_to_rev[linknode]
254 linkrev = self.clnode_to_rev[linknode]
267 self.clrev_to_localrev[linkrev] = rev
255 self.clrev_to_localrev[linkrev] = rev
268 # This is a node to send in full, because the changeset it
256 # This is a node to send in full, because the changeset it
269 # corresponds to was a full changeset.
257 # corresponds to was a full changeset.
270 if linknode in self.full_nodes:
258 if linknode in self.full_nodes:
271 for x in orig(self, revlog, rev, prev, linknode):
259 for x in orig(self, revlog, rev, prev, linknode):
272 yield x
260 yield x
273 return
261 return
274 # At this point, a node can either be one we should skip or an
262 # At this point, a node can either be one we should skip or an
275 # ellipsis. If it's not an ellipsis, bail immediately.
263 # ellipsis. If it's not an ellipsis, bail immediately.
276 if linkrev not in self.precomputed_ellipsis:
264 if linkrev not in self.precomputed_ellipsis:
277 return
265 return
278 linkparents = self.precomputed_ellipsis[linkrev]
266 linkparents = self.precomputed_ellipsis[linkrev]
279 def local(clrev):
267 def local(clrev):
280 """Turn a changelog revnum into a local revnum.
268 """Turn a changelog revnum into a local revnum.
281
269
282 The ellipsis dag is stored as revnums on the changelog,
270 The ellipsis dag is stored as revnums on the changelog,
283 but when we're producing ellipsis entries for
271 but when we're producing ellipsis entries for
284 non-changelog revlogs, we need to turn those numbers into
272 non-changelog revlogs, we need to turn those numbers into
285 something local. This does that for us, and during the
273 something local. This does that for us, and during the
286 changelog sending phase will also expand the stored
274 changelog sending phase will also expand the stored
287 mappings as needed.
275 mappings as needed.
288 """
276 """
289 if clrev == node.nullrev:
277 if clrev == node.nullrev:
290 return node.nullrev
278 return node.nullrev
291 if not self.changelog_done:
279 if not self.changelog_done:
292 # If we're doing the changelog, it's possible that we
280 # If we're doing the changelog, it's possible that we
293 # have a parent that is already on the client, and we
281 # have a parent that is already on the client, and we
294 # need to store some extra mapping information so that
282 # need to store some extra mapping information so that
295 # our contained ellipsis nodes will be able to resolve
283 # our contained ellipsis nodes will be able to resolve
296 # their parents.
284 # their parents.
297 if clrev not in self.clrev_to_localrev:
285 if clrev not in self.clrev_to_localrev:
298 clnode = revlog.node(clrev)
286 clnode = revlog.node(clrev)
299 self.clnode_to_rev[clnode] = clrev
287 self.clnode_to_rev[clnode] = clrev
300 return clrev
288 return clrev
301 # Walk the ellipsis-ized changelog breadth-first looking for a
289 # Walk the ellipsis-ized changelog breadth-first looking for a
302 # change that has been linked from the current revlog.
290 # change that has been linked from the current revlog.
303 #
291 #
304 # For a flat manifest revlog only a single step should be necessary
292 # For a flat manifest revlog only a single step should be necessary
305 # as all relevant changelog entries are relevant to the flat
293 # as all relevant changelog entries are relevant to the flat
306 # manifest.
294 # manifest.
307 #
295 #
308 # For a filelog or tree manifest dirlog however not every changelog
296 # For a filelog or tree manifest dirlog however not every changelog
309 # entry will have been relevant, so we need to skip some changelog
297 # entry will have been relevant, so we need to skip some changelog
310 # nodes even after ellipsis-izing.
298 # nodes even after ellipsis-izing.
311 walk = [clrev]
299 walk = [clrev]
312 while walk:
300 while walk:
313 p = walk[0]
301 p = walk[0]
314 walk = walk[1:]
302 walk = walk[1:]
315 if p in self.clrev_to_localrev:
303 if p in self.clrev_to_localrev:
316 return self.clrev_to_localrev[p]
304 return self.clrev_to_localrev[p]
317 elif p in self.full_nodes:
305 elif p in self.full_nodes:
318 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
306 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
319 if pp != node.nullrev])
307 if pp != node.nullrev])
320 elif p in self.precomputed_ellipsis:
308 elif p in self.precomputed_ellipsis:
321 walk.extend([pp for pp in self.precomputed_ellipsis[p]
309 walk.extend([pp for pp in self.precomputed_ellipsis[p]
322 if pp != node.nullrev])
310 if pp != node.nullrev])
323 else:
311 else:
324 # In this case, we've got an ellipsis with parents
312 # In this case, we've got an ellipsis with parents
325 # outside the current bundle (likely an
313 # outside the current bundle (likely an
326 # incremental pull). We "know" that we can use the
314 # incremental pull). We "know" that we can use the
327 # value of this same revlog at whatever revision
315 # value of this same revlog at whatever revision
328 # is pointed to by linknode. "Know" is in scare
316 # is pointed to by linknode. "Know" is in scare
329 # quotes because I haven't done enough examination
317 # quotes because I haven't done enough examination
330 # of edge cases to convince myself this is really
318 # of edge cases to convince myself this is really
331 # a fact - it works for all the (admittedly
319 # a fact - it works for all the (admittedly
332 # thorough) cases in our testsuite, but I would be
320 # thorough) cases in our testsuite, but I would be
333 # somewhat unsurprised to find a case in the wild
321 # somewhat unsurprised to find a case in the wild
334 # where this breaks down a bit. That said, I don't
322 # where this breaks down a bit. That said, I don't
335 # know if it would hurt anything.
323 # know if it would hurt anything.
336 for i in pycompat.xrange(rev, 0, -1):
324 for i in pycompat.xrange(rev, 0, -1):
337 if revlog.linkrev(i) == clrev:
325 if revlog.linkrev(i) == clrev:
338 return i
326 return i
339 # We failed to resolve a parent for this node, so
327 # We failed to resolve a parent for this node, so
340 # we crash the changegroup construction.
328 # we crash the changegroup construction.
341 raise error.Abort(
329 raise error.Abort(
342 'unable to resolve parent while packing %r %r'
330 'unable to resolve parent while packing %r %r'
343 ' for changeset %r' % (revlog.indexfile, rev, clrev))
331 ' for changeset %r' % (revlog.indexfile, rev, clrev))
344 return node.nullrev
332 return node.nullrev
345
333
346 if not linkparents or (
334 if not linkparents or (
347 revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
335 revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
348 p1, p2 = node.nullrev, node.nullrev
336 p1, p2 = node.nullrev, node.nullrev
349 elif len(linkparents) == 1:
337 elif len(linkparents) == 1:
350 p1, = sorted(local(p) for p in linkparents)
338 p1, = sorted(local(p) for p in linkparents)
351 p2 = node.nullrev
339 p2 = node.nullrev
352 else:
340 else:
353 p1, p2 = sorted(local(p) for p in linkparents)
341 p1, p2 = sorted(local(p) for p in linkparents)
354 n = revlog.node(rev)
342 n = revlog.node(rev)
355 yield ellipsisdata(
343 yield ellipsisdata(
356 self, rev, revlog, p1, p2, revlog.revision(n), linknode)
344 self, rev, revlog, p1, p2, revlog.revision(n), linknode)
357 extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
345 extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
358
346
359 def deltaparent(orig, self, revlog, rev, p1, p2, prev):
347 def deltaparent(orig, self, revlog, rev, p1, p2, prev):
360 if util.safehasattr(self, 'full_nodes'):
348 if util.safehasattr(self, 'full_nodes'):
361 # TODO: send better deltas when in narrow mode.
349 # TODO: send better deltas when in narrow mode.
362 #
350 #
363 # changegroup.group() loops over revisions to send,
351 # changegroup.group() loops over revisions to send,
364 # including revisions we'll skip. What this means is that
352 # including revisions we'll skip. What this means is that
365 # `prev` will be a potentially useless delta base for all
353 # `prev` will be a potentially useless delta base for all
366 # ellipsis nodes, as the client likely won't have it. In
354 # ellipsis nodes, as the client likely won't have it. In
367 # the future we should do bookkeeping about which nodes
355 # the future we should do bookkeeping about which nodes
368 # have been sent to the client, and try to be
356 # have been sent to the client, and try to be
369 # significantly smarter about delta bases. This is
357 # significantly smarter about delta bases. This is
370 # slightly tricky because this same code has to work for
358 # slightly tricky because this same code has to work for
371 # all revlogs, and we don't have the linkrev/linknode here.
359 # all revlogs, and we don't have the linkrev/linknode here.
372 return p1
360 return p1
373 return orig(self, revlog, rev, p1, p2, prev)
361 return orig(self, revlog, rev, p1, p2, prev)
374 extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
362 extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
@@ -1,1013 +1,1037 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullrev,
17 nullrev,
18 short,
18 short,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 dagutil,
22 dagutil,
23 error,
23 error,
24 match as matchmod,
24 mdiff,
25 mdiff,
25 phases,
26 phases,
26 pycompat,
27 pycompat,
27 util,
28 util,
28 )
29 )
29
30
30 from .utils import (
31 from .utils import (
31 stringutil,
32 stringutil,
32 )
33 )
33
34
34 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
35 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
35 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
36 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
36 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
37 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
37
38
38 LFS_REQUIREMENT = 'lfs'
39 LFS_REQUIREMENT = 'lfs'
39
40
40 # When narrowing is finalized and no longer subject to format changes,
41 # When narrowing is finalized and no longer subject to format changes,
41 # we should move this to just "narrow" or similar.
42 # we should move this to just "narrow" or similar.
42 NARROW_REQUIREMENT = 'narrowhg-experimental'
43 NARROW_REQUIREMENT = 'narrowhg-experimental'
43
44
44 readexactly = util.readexactly
45 readexactly = util.readexactly
45
46
46 def getchunk(stream):
47 def getchunk(stream):
47 """return the next chunk from stream as a string"""
48 """return the next chunk from stream as a string"""
48 d = readexactly(stream, 4)
49 d = readexactly(stream, 4)
49 l = struct.unpack(">l", d)[0]
50 l = struct.unpack(">l", d)[0]
50 if l <= 4:
51 if l <= 4:
51 if l:
52 if l:
52 raise error.Abort(_("invalid chunk length %d") % l)
53 raise error.Abort(_("invalid chunk length %d") % l)
53 return ""
54 return ""
54 return readexactly(stream, l - 4)
55 return readexactly(stream, l - 4)
55
56
56 def chunkheader(length):
57 def chunkheader(length):
57 """return a changegroup chunk header (string)"""
58 """return a changegroup chunk header (string)"""
58 return struct.pack(">l", length + 4)
59 return struct.pack(">l", length + 4)
59
60
60 def closechunk():
61 def closechunk():
61 """return a changegroup chunk header (string) for a zero-length chunk"""
62 """return a changegroup chunk header (string) for a zero-length chunk"""
62 return struct.pack(">l", 0)
63 return struct.pack(">l", 0)
63
64
64 def writechunks(ui, chunks, filename, vfs=None):
65 def writechunks(ui, chunks, filename, vfs=None):
65 """Write chunks to a file and return its filename.
66 """Write chunks to a file and return its filename.
66
67
67 The stream is assumed to be a bundle file.
68 The stream is assumed to be a bundle file.
68 Existing files will not be overwritten.
69 Existing files will not be overwritten.
69 If no filename is specified, a temporary file is created.
70 If no filename is specified, a temporary file is created.
70 """
71 """
71 fh = None
72 fh = None
72 cleanup = None
73 cleanup = None
73 try:
74 try:
74 if filename:
75 if filename:
75 if vfs:
76 if vfs:
76 fh = vfs.open(filename, "wb")
77 fh = vfs.open(filename, "wb")
77 else:
78 else:
78 # Increase default buffer size because default is usually
79 # Increase default buffer size because default is usually
79 # small (4k is common on Linux).
80 # small (4k is common on Linux).
80 fh = open(filename, "wb", 131072)
81 fh = open(filename, "wb", 131072)
81 else:
82 else:
82 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
83 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
83 fh = os.fdopen(fd, r"wb")
84 fh = os.fdopen(fd, r"wb")
84 cleanup = filename
85 cleanup = filename
85 for c in chunks:
86 for c in chunks:
86 fh.write(c)
87 fh.write(c)
87 cleanup = None
88 cleanup = None
88 return filename
89 return filename
89 finally:
90 finally:
90 if fh is not None:
91 if fh is not None:
91 fh.close()
92 fh.close()
92 if cleanup is not None:
93 if cleanup is not None:
93 if filename and vfs:
94 if filename and vfs:
94 vfs.unlink(cleanup)
95 vfs.unlink(cleanup)
95 else:
96 else:
96 os.unlink(cleanup)
97 os.unlink(cleanup)
97
98
98 class cg1unpacker(object):
99 class cg1unpacker(object):
99 """Unpacker for cg1 changegroup streams.
100 """Unpacker for cg1 changegroup streams.
100
101
101 A changegroup unpacker handles the framing of the revision data in
102 A changegroup unpacker handles the framing of the revision data in
102 the wire format. Most consumers will want to use the apply()
103 the wire format. Most consumers will want to use the apply()
103 method to add the changes from the changegroup to a repository.
104 method to add the changes from the changegroup to a repository.
104
105
105 If you're forwarding a changegroup unmodified to another consumer,
106 If you're forwarding a changegroup unmodified to another consumer,
106 use getchunks(), which returns an iterator of changegroup
107 use getchunks(), which returns an iterator of changegroup
107 chunks. This is mostly useful for cases where you need to know the
108 chunks. This is mostly useful for cases where you need to know the
108 data stream has ended by observing the end of the changegroup.
109 data stream has ended by observing the end of the changegroup.
109
110
110 deltachunk() is useful only if you're applying delta data. Most
111 deltachunk() is useful only if you're applying delta data. Most
111 consumers should prefer apply() instead.
112 consumers should prefer apply() instead.
112
113
113 A few other public methods exist. Those are used only for
114 A few other public methods exist. Those are used only for
114 bundlerepo and some debug commands - their use is discouraged.
115 bundlerepo and some debug commands - their use is discouraged.
115 """
116 """
116 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
117 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
117 deltaheadersize = struct.calcsize(deltaheader)
118 deltaheadersize = struct.calcsize(deltaheader)
118 version = '01'
119 version = '01'
119 _grouplistcount = 1 # One list of files after the manifests
120 _grouplistcount = 1 # One list of files after the manifests
120
121
121 def __init__(self, fh, alg, extras=None):
122 def __init__(self, fh, alg, extras=None):
122 if alg is None:
123 if alg is None:
123 alg = 'UN'
124 alg = 'UN'
124 if alg not in util.compengines.supportedbundletypes:
125 if alg not in util.compengines.supportedbundletypes:
125 raise error.Abort(_('unknown stream compression type: %s')
126 raise error.Abort(_('unknown stream compression type: %s')
126 % alg)
127 % alg)
127 if alg == 'BZ':
128 if alg == 'BZ':
128 alg = '_truncatedBZ'
129 alg = '_truncatedBZ'
129
130
130 compengine = util.compengines.forbundletype(alg)
131 compengine = util.compengines.forbundletype(alg)
131 self._stream = compengine.decompressorreader(fh)
132 self._stream = compengine.decompressorreader(fh)
132 self._type = alg
133 self._type = alg
133 self.extras = extras or {}
134 self.extras = extras or {}
134 self.callback = None
135 self.callback = None
135
136
136 # These methods (compressed, read, seek, tell) all appear to only
137 # These methods (compressed, read, seek, tell) all appear to only
137 # be used by bundlerepo, but it's a little hard to tell.
138 # be used by bundlerepo, but it's a little hard to tell.
138 def compressed(self):
139 def compressed(self):
139 return self._type is not None and self._type != 'UN'
140 return self._type is not None and self._type != 'UN'
140 def read(self, l):
141 def read(self, l):
141 return self._stream.read(l)
142 return self._stream.read(l)
142 def seek(self, pos):
143 def seek(self, pos):
143 return self._stream.seek(pos)
144 return self._stream.seek(pos)
144 def tell(self):
145 def tell(self):
145 return self._stream.tell()
146 return self._stream.tell()
146 def close(self):
147 def close(self):
147 return self._stream.close()
148 return self._stream.close()
148
149
149 def _chunklength(self):
150 def _chunklength(self):
150 d = readexactly(self._stream, 4)
151 d = readexactly(self._stream, 4)
151 l = struct.unpack(">l", d)[0]
152 l = struct.unpack(">l", d)[0]
152 if l <= 4:
153 if l <= 4:
153 if l:
154 if l:
154 raise error.Abort(_("invalid chunk length %d") % l)
155 raise error.Abort(_("invalid chunk length %d") % l)
155 return 0
156 return 0
156 if self.callback:
157 if self.callback:
157 self.callback()
158 self.callback()
158 return l - 4
159 return l - 4
159
160
160 def changelogheader(self):
161 def changelogheader(self):
161 """v10 does not have a changelog header chunk"""
162 """v10 does not have a changelog header chunk"""
162 return {}
163 return {}
163
164
164 def manifestheader(self):
165 def manifestheader(self):
165 """v10 does not have a manifest header chunk"""
166 """v10 does not have a manifest header chunk"""
166 return {}
167 return {}
167
168
168 def filelogheader(self):
169 def filelogheader(self):
169 """return the header of the filelogs chunk, v10 only has the filename"""
170 """return the header of the filelogs chunk, v10 only has the filename"""
170 l = self._chunklength()
171 l = self._chunklength()
171 if not l:
172 if not l:
172 return {}
173 return {}
173 fname = readexactly(self._stream, l)
174 fname = readexactly(self._stream, l)
174 return {'filename': fname}
175 return {'filename': fname}
175
176
176 def _deltaheader(self, headertuple, prevnode):
177 def _deltaheader(self, headertuple, prevnode):
177 node, p1, p2, cs = headertuple
178 node, p1, p2, cs = headertuple
178 if prevnode is None:
179 if prevnode is None:
179 deltabase = p1
180 deltabase = p1
180 else:
181 else:
181 deltabase = prevnode
182 deltabase = prevnode
182 flags = 0
183 flags = 0
183 return node, p1, p2, deltabase, cs, flags
184 return node, p1, p2, deltabase, cs, flags
184
185
185 def deltachunk(self, prevnode):
186 def deltachunk(self, prevnode):
186 l = self._chunklength()
187 l = self._chunklength()
187 if not l:
188 if not l:
188 return {}
189 return {}
189 headerdata = readexactly(self._stream, self.deltaheadersize)
190 headerdata = readexactly(self._stream, self.deltaheadersize)
190 header = struct.unpack(self.deltaheader, headerdata)
191 header = struct.unpack(self.deltaheader, headerdata)
191 delta = readexactly(self._stream, l - self.deltaheadersize)
192 delta = readexactly(self._stream, l - self.deltaheadersize)
192 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
193 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
193 return (node, p1, p2, cs, deltabase, delta, flags)
194 return (node, p1, p2, cs, deltabase, delta, flags)
194
195
195 def getchunks(self):
196 def getchunks(self):
196 """returns all the chunks contains in the bundle
197 """returns all the chunks contains in the bundle
197
198
198 Used when you need to forward the binary stream to a file or another
199 Used when you need to forward the binary stream to a file or another
199 network API. To do so, it parse the changegroup data, otherwise it will
200 network API. To do so, it parse the changegroup data, otherwise it will
200 block in case of sshrepo because it don't know the end of the stream.
201 block in case of sshrepo because it don't know the end of the stream.
201 """
202 """
202 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
203 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
203 # and a list of filelogs. For changegroup 3, we expect 4 parts:
204 # and a list of filelogs. For changegroup 3, we expect 4 parts:
204 # changelog, manifestlog, a list of tree manifestlogs, and a list of
205 # changelog, manifestlog, a list of tree manifestlogs, and a list of
205 # filelogs.
206 # filelogs.
206 #
207 #
207 # Changelog and manifestlog parts are terminated with empty chunks. The
208 # Changelog and manifestlog parts are terminated with empty chunks. The
208 # tree and file parts are a list of entry sections. Each entry section
209 # tree and file parts are a list of entry sections. Each entry section
209 # is a series of chunks terminating in an empty chunk. The list of these
210 # is a series of chunks terminating in an empty chunk. The list of these
210 # entry sections is terminated in yet another empty chunk, so we know
211 # entry sections is terminated in yet another empty chunk, so we know
211 # we've reached the end of the tree/file list when we reach an empty
212 # we've reached the end of the tree/file list when we reach an empty
212 # chunk that was proceeded by no non-empty chunks.
213 # chunk that was proceeded by no non-empty chunks.
213
214
214 parts = 0
215 parts = 0
215 while parts < 2 + self._grouplistcount:
216 while parts < 2 + self._grouplistcount:
216 noentries = True
217 noentries = True
217 while True:
218 while True:
218 chunk = getchunk(self)
219 chunk = getchunk(self)
219 if not chunk:
220 if not chunk:
220 # The first two empty chunks represent the end of the
221 # The first two empty chunks represent the end of the
221 # changelog and the manifestlog portions. The remaining
222 # changelog and the manifestlog portions. The remaining
222 # empty chunks represent either A) the end of individual
223 # empty chunks represent either A) the end of individual
223 # tree or file entries in the file list, or B) the end of
224 # tree or file entries in the file list, or B) the end of
224 # the entire list. It's the end of the entire list if there
225 # the entire list. It's the end of the entire list if there
225 # were no entries (i.e. noentries is True).
226 # were no entries (i.e. noentries is True).
226 if parts < 2:
227 if parts < 2:
227 parts += 1
228 parts += 1
228 elif noentries:
229 elif noentries:
229 parts += 1
230 parts += 1
230 break
231 break
231 noentries = False
232 noentries = False
232 yield chunkheader(len(chunk))
233 yield chunkheader(len(chunk))
233 pos = 0
234 pos = 0
234 while pos < len(chunk):
235 while pos < len(chunk):
235 next = pos + 2**20
236 next = pos + 2**20
236 yield chunk[pos:next]
237 yield chunk[pos:next]
237 pos = next
238 pos = next
238 yield closechunk()
239 yield closechunk()
239
240
240 def _unpackmanifests(self, repo, revmap, trp, prog):
241 def _unpackmanifests(self, repo, revmap, trp, prog):
241 self.callback = prog.increment
242 self.callback = prog.increment
242 # no need to check for empty manifest group here:
243 # no need to check for empty manifest group here:
243 # if the result of the merge of 1 and 2 is the same in 3 and 4,
244 # if the result of the merge of 1 and 2 is the same in 3 and 4,
244 # no new manifest will be created and the manifest group will
245 # no new manifest will be created and the manifest group will
245 # be empty during the pull
246 # be empty during the pull
246 self.manifestheader()
247 self.manifestheader()
247 deltas = self.deltaiter()
248 deltas = self.deltaiter()
248 repo.manifestlog.addgroup(deltas, revmap, trp)
249 repo.manifestlog.addgroup(deltas, revmap, trp)
249 prog.complete()
250 prog.complete()
250 self.callback = None
251 self.callback = None
251
252
252 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
253 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
253 expectedtotal=None):
254 expectedtotal=None):
254 """Add the changegroup returned by source.read() to this repo.
255 """Add the changegroup returned by source.read() to this repo.
255 srctype is a string like 'push', 'pull', or 'unbundle'. url is
256 srctype is a string like 'push', 'pull', or 'unbundle'. url is
256 the URL of the repo where this changegroup is coming from.
257 the URL of the repo where this changegroup is coming from.
257
258
258 Return an integer summarizing the change to this repo:
259 Return an integer summarizing the change to this repo:
259 - nothing changed or no source: 0
260 - nothing changed or no source: 0
260 - more heads than before: 1+added heads (2..n)
261 - more heads than before: 1+added heads (2..n)
261 - fewer heads than before: -1-removed heads (-2..-n)
262 - fewer heads than before: -1-removed heads (-2..-n)
262 - number of heads stays the same: 1
263 - number of heads stays the same: 1
263 """
264 """
264 repo = repo.unfiltered()
265 repo = repo.unfiltered()
265 def csmap(x):
266 def csmap(x):
266 repo.ui.debug("add changeset %s\n" % short(x))
267 repo.ui.debug("add changeset %s\n" % short(x))
267 return len(cl)
268 return len(cl)
268
269
269 def revmap(x):
270 def revmap(x):
270 return cl.rev(x)
271 return cl.rev(x)
271
272
272 changesets = files = revisions = 0
273 changesets = files = revisions = 0
273
274
274 try:
275 try:
275 # The transaction may already carry source information. In this
276 # The transaction may already carry source information. In this
276 # case we use the top level data. We overwrite the argument
277 # case we use the top level data. We overwrite the argument
277 # because we need to use the top level value (if they exist)
278 # because we need to use the top level value (if they exist)
278 # in this function.
279 # in this function.
279 srctype = tr.hookargs.setdefault('source', srctype)
280 srctype = tr.hookargs.setdefault('source', srctype)
280 url = tr.hookargs.setdefault('url', url)
281 url = tr.hookargs.setdefault('url', url)
281 repo.hook('prechangegroup',
282 repo.hook('prechangegroup',
282 throw=True, **pycompat.strkwargs(tr.hookargs))
283 throw=True, **pycompat.strkwargs(tr.hookargs))
283
284
284 # write changelog data to temp files so concurrent readers
285 # write changelog data to temp files so concurrent readers
285 # will not see an inconsistent view
286 # will not see an inconsistent view
286 cl = repo.changelog
287 cl = repo.changelog
287 cl.delayupdate(tr)
288 cl.delayupdate(tr)
288 oldheads = set(cl.heads())
289 oldheads = set(cl.heads())
289
290
290 trp = weakref.proxy(tr)
291 trp = weakref.proxy(tr)
291 # pull off the changeset group
292 # pull off the changeset group
292 repo.ui.status(_("adding changesets\n"))
293 repo.ui.status(_("adding changesets\n"))
293 clstart = len(cl)
294 clstart = len(cl)
294 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
295 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
295 total=expectedtotal)
296 total=expectedtotal)
296 self.callback = progress.increment
297 self.callback = progress.increment
297
298
298 efiles = set()
299 efiles = set()
299 def onchangelog(cl, node):
300 def onchangelog(cl, node):
300 efiles.update(cl.readfiles(node))
301 efiles.update(cl.readfiles(node))
301
302
302 self.changelogheader()
303 self.changelogheader()
303 deltas = self.deltaiter()
304 deltas = self.deltaiter()
304 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
305 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
305 efiles = len(efiles)
306 efiles = len(efiles)
306
307
307 if not cgnodes:
308 if not cgnodes:
308 repo.ui.develwarn('applied empty changegroup',
309 repo.ui.develwarn('applied empty changegroup',
309 config='warn-empty-changegroup')
310 config='warn-empty-changegroup')
310 clend = len(cl)
311 clend = len(cl)
311 changesets = clend - clstart
312 changesets = clend - clstart
312 progress.complete()
313 progress.complete()
313 self.callback = None
314 self.callback = None
314
315
315 # pull off the manifest group
316 # pull off the manifest group
316 repo.ui.status(_("adding manifests\n"))
317 repo.ui.status(_("adding manifests\n"))
317 # We know that we'll never have more manifests than we had
318 # We know that we'll never have more manifests than we had
318 # changesets.
319 # changesets.
319 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
320 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
320 total=changesets)
321 total=changesets)
321 self._unpackmanifests(repo, revmap, trp, progress)
322 self._unpackmanifests(repo, revmap, trp, progress)
322
323
323 needfiles = {}
324 needfiles = {}
324 if repo.ui.configbool('server', 'validate'):
325 if repo.ui.configbool('server', 'validate'):
325 cl = repo.changelog
326 cl = repo.changelog
326 ml = repo.manifestlog
327 ml = repo.manifestlog
327 # validate incoming csets have their manifests
328 # validate incoming csets have their manifests
328 for cset in pycompat.xrange(clstart, clend):
329 for cset in pycompat.xrange(clstart, clend):
329 mfnode = cl.changelogrevision(cset).manifest
330 mfnode = cl.changelogrevision(cset).manifest
330 mfest = ml[mfnode].readdelta()
331 mfest = ml[mfnode].readdelta()
331 # store file cgnodes we must see
332 # store file cgnodes we must see
332 for f, n in mfest.iteritems():
333 for f, n in mfest.iteritems():
333 needfiles.setdefault(f, set()).add(n)
334 needfiles.setdefault(f, set()).add(n)
334
335
335 # process the files
336 # process the files
336 repo.ui.status(_("adding file changes\n"))
337 repo.ui.status(_("adding file changes\n"))
337 newrevs, newfiles = _addchangegroupfiles(
338 newrevs, newfiles = _addchangegroupfiles(
338 repo, self, revmap, trp, efiles, needfiles)
339 repo, self, revmap, trp, efiles, needfiles)
339 revisions += newrevs
340 revisions += newrevs
340 files += newfiles
341 files += newfiles
341
342
342 deltaheads = 0
343 deltaheads = 0
343 if oldheads:
344 if oldheads:
344 heads = cl.heads()
345 heads = cl.heads()
345 deltaheads = len(heads) - len(oldheads)
346 deltaheads = len(heads) - len(oldheads)
346 for h in heads:
347 for h in heads:
347 if h not in oldheads and repo[h].closesbranch():
348 if h not in oldheads and repo[h].closesbranch():
348 deltaheads -= 1
349 deltaheads -= 1
349 htext = ""
350 htext = ""
350 if deltaheads:
351 if deltaheads:
351 htext = _(" (%+d heads)") % deltaheads
352 htext = _(" (%+d heads)") % deltaheads
352
353
353 repo.ui.status(_("added %d changesets"
354 repo.ui.status(_("added %d changesets"
354 " with %d changes to %d files%s\n")
355 " with %d changes to %d files%s\n")
355 % (changesets, revisions, files, htext))
356 % (changesets, revisions, files, htext))
356 repo.invalidatevolatilesets()
357 repo.invalidatevolatilesets()
357
358
358 if changesets > 0:
359 if changesets > 0:
359 if 'node' not in tr.hookargs:
360 if 'node' not in tr.hookargs:
360 tr.hookargs['node'] = hex(cl.node(clstart))
361 tr.hookargs['node'] = hex(cl.node(clstart))
361 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
362 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
362 hookargs = dict(tr.hookargs)
363 hookargs = dict(tr.hookargs)
363 else:
364 else:
364 hookargs = dict(tr.hookargs)
365 hookargs = dict(tr.hookargs)
365 hookargs['node'] = hex(cl.node(clstart))
366 hookargs['node'] = hex(cl.node(clstart))
366 hookargs['node_last'] = hex(cl.node(clend - 1))
367 hookargs['node_last'] = hex(cl.node(clend - 1))
367 repo.hook('pretxnchangegroup',
368 repo.hook('pretxnchangegroup',
368 throw=True, **pycompat.strkwargs(hookargs))
369 throw=True, **pycompat.strkwargs(hookargs))
369
370
370 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
371 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
371 phaseall = None
372 phaseall = None
372 if srctype in ('push', 'serve'):
373 if srctype in ('push', 'serve'):
373 # Old servers can not push the boundary themselves.
374 # Old servers can not push the boundary themselves.
374 # New servers won't push the boundary if changeset already
375 # New servers won't push the boundary if changeset already
375 # exists locally as secret
376 # exists locally as secret
376 #
377 #
377 # We should not use added here but the list of all change in
378 # We should not use added here but the list of all change in
378 # the bundle
379 # the bundle
379 if repo.publishing():
380 if repo.publishing():
380 targetphase = phaseall = phases.public
381 targetphase = phaseall = phases.public
381 else:
382 else:
382 # closer target phase computation
383 # closer target phase computation
383
384
384 # Those changesets have been pushed from the
385 # Those changesets have been pushed from the
385 # outside, their phases are going to be pushed
386 # outside, their phases are going to be pushed
386 # alongside. Therefor `targetphase` is
387 # alongside. Therefor `targetphase` is
387 # ignored.
388 # ignored.
388 targetphase = phaseall = phases.draft
389 targetphase = phaseall = phases.draft
389 if added:
390 if added:
390 phases.registernew(repo, tr, targetphase, added)
391 phases.registernew(repo, tr, targetphase, added)
391 if phaseall is not None:
392 if phaseall is not None:
392 phases.advanceboundary(repo, tr, phaseall, cgnodes)
393 phases.advanceboundary(repo, tr, phaseall, cgnodes)
393
394
394 if changesets > 0:
395 if changesets > 0:
395
396
396 def runhooks():
397 def runhooks():
397 # These hooks run when the lock releases, not when the
398 # These hooks run when the lock releases, not when the
398 # transaction closes. So it's possible for the changelog
399 # transaction closes. So it's possible for the changelog
399 # to have changed since we last saw it.
400 # to have changed since we last saw it.
400 if clstart >= len(repo):
401 if clstart >= len(repo):
401 return
402 return
402
403
403 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
404 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
404
405
405 for n in added:
406 for n in added:
406 args = hookargs.copy()
407 args = hookargs.copy()
407 args['node'] = hex(n)
408 args['node'] = hex(n)
408 del args['node_last']
409 del args['node_last']
409 repo.hook("incoming", **pycompat.strkwargs(args))
410 repo.hook("incoming", **pycompat.strkwargs(args))
410
411
411 newheads = [h for h in repo.heads()
412 newheads = [h for h in repo.heads()
412 if h not in oldheads]
413 if h not in oldheads]
413 repo.ui.log("incoming",
414 repo.ui.log("incoming",
414 "%d incoming changes - new heads: %s\n",
415 "%d incoming changes - new heads: %s\n",
415 len(added),
416 len(added),
416 ', '.join([hex(c[:6]) for c in newheads]))
417 ', '.join([hex(c[:6]) for c in newheads]))
417
418
418 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
419 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
419 lambda tr: repo._afterlock(runhooks))
420 lambda tr: repo._afterlock(runhooks))
420 finally:
421 finally:
421 repo.ui.flush()
422 repo.ui.flush()
422 # never return 0 here:
423 # never return 0 here:
423 if deltaheads < 0:
424 if deltaheads < 0:
424 ret = deltaheads - 1
425 ret = deltaheads - 1
425 else:
426 else:
426 ret = deltaheads + 1
427 ret = deltaheads + 1
427 return ret
428 return ret
428
429
429 def deltaiter(self):
430 def deltaiter(self):
430 """
431 """
431 returns an iterator of the deltas in this changegroup
432 returns an iterator of the deltas in this changegroup
432
433
433 Useful for passing to the underlying storage system to be stored.
434 Useful for passing to the underlying storage system to be stored.
434 """
435 """
435 chain = None
436 chain = None
436 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
437 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
437 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
438 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
438 yield chunkdata
439 yield chunkdata
439 chain = chunkdata[0]
440 chain = chunkdata[0]
440
441
441 class cg2unpacker(cg1unpacker):
442 class cg2unpacker(cg1unpacker):
442 """Unpacker for cg2 streams.
443 """Unpacker for cg2 streams.
443
444
444 cg2 streams add support for generaldelta, so the delta header
445 cg2 streams add support for generaldelta, so the delta header
445 format is slightly different. All other features about the data
446 format is slightly different. All other features about the data
446 remain the same.
447 remain the same.
447 """
448 """
448 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
449 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
449 deltaheadersize = struct.calcsize(deltaheader)
450 deltaheadersize = struct.calcsize(deltaheader)
450 version = '02'
451 version = '02'
451
452
452 def _deltaheader(self, headertuple, prevnode):
453 def _deltaheader(self, headertuple, prevnode):
453 node, p1, p2, deltabase, cs = headertuple
454 node, p1, p2, deltabase, cs = headertuple
454 flags = 0
455 flags = 0
455 return node, p1, p2, deltabase, cs, flags
456 return node, p1, p2, deltabase, cs, flags
456
457
457 class cg3unpacker(cg2unpacker):
458 class cg3unpacker(cg2unpacker):
458 """Unpacker for cg3 streams.
459 """Unpacker for cg3 streams.
459
460
460 cg3 streams add support for exchanging treemanifests and revlog
461 cg3 streams add support for exchanging treemanifests and revlog
461 flags. It adds the revlog flags to the delta header and an empty chunk
462 flags. It adds the revlog flags to the delta header and an empty chunk
462 separating manifests and files.
463 separating manifests and files.
463 """
464 """
464 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
465 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
465 deltaheadersize = struct.calcsize(deltaheader)
466 deltaheadersize = struct.calcsize(deltaheader)
466 version = '03'
467 version = '03'
467 _grouplistcount = 2 # One list of manifests and one list of files
468 _grouplistcount = 2 # One list of manifests and one list of files
468
469
469 def _deltaheader(self, headertuple, prevnode):
470 def _deltaheader(self, headertuple, prevnode):
470 node, p1, p2, deltabase, cs, flags = headertuple
471 node, p1, p2, deltabase, cs, flags = headertuple
471 return node, p1, p2, deltabase, cs, flags
472 return node, p1, p2, deltabase, cs, flags
472
473
473 def _unpackmanifests(self, repo, revmap, trp, prog):
474 def _unpackmanifests(self, repo, revmap, trp, prog):
474 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
475 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
475 for chunkdata in iter(self.filelogheader, {}):
476 for chunkdata in iter(self.filelogheader, {}):
476 # If we get here, there are directory manifests in the changegroup
477 # If we get here, there are directory manifests in the changegroup
477 d = chunkdata["filename"]
478 d = chunkdata["filename"]
478 repo.ui.debug("adding %s revisions\n" % d)
479 repo.ui.debug("adding %s revisions\n" % d)
479 dirlog = repo.manifestlog._revlog.dirlog(d)
480 dirlog = repo.manifestlog._revlog.dirlog(d)
480 deltas = self.deltaiter()
481 deltas = self.deltaiter()
481 if not dirlog.addgroup(deltas, revmap, trp):
482 if not dirlog.addgroup(deltas, revmap, trp):
482 raise error.Abort(_("received dir revlog group is empty"))
483 raise error.Abort(_("received dir revlog group is empty"))
483
484
484 class headerlessfixup(object):
485 class headerlessfixup(object):
485 def __init__(self, fh, h):
486 def __init__(self, fh, h):
486 self._h = h
487 self._h = h
487 self._fh = fh
488 self._fh = fh
488 def read(self, n):
489 def read(self, n):
489 if self._h:
490 if self._h:
490 d, self._h = self._h[:n], self._h[n:]
491 d, self._h = self._h[:n], self._h[n:]
491 if len(d) < n:
492 if len(d) < n:
492 d += readexactly(self._fh, n - len(d))
493 d += readexactly(self._fh, n - len(d))
493 return d
494 return d
494 return readexactly(self._fh, n)
495 return readexactly(self._fh, n)
495
496
496 class cg1packer(object):
497 class cg1packer(object):
497 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
498 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
498 version = '01'
499 version = '01'
499 def __init__(self, repo, bundlecaps=None):
500 def __init__(self, repo, filematcher, bundlecaps=None):
500 """Given a source repo, construct a bundler.
501 """Given a source repo, construct a bundler.
501
502
503 filematcher is a matcher that matches on files to include in the
504 changegroup. Used to facilitate sparse changegroups.
505
502 bundlecaps is optional and can be used to specify the set of
506 bundlecaps is optional and can be used to specify the set of
503 capabilities which can be used to build the bundle. While bundlecaps is
507 capabilities which can be used to build the bundle. While bundlecaps is
504 unused in core Mercurial, extensions rely on this feature to communicate
508 unused in core Mercurial, extensions rely on this feature to communicate
505 capabilities to customize the changegroup packer.
509 capabilities to customize the changegroup packer.
506 """
510 """
511 assert filematcher
512 self._filematcher = filematcher
513
507 # Set of capabilities we can use to build the bundle.
514 # Set of capabilities we can use to build the bundle.
508 if bundlecaps is None:
515 if bundlecaps is None:
509 bundlecaps = set()
516 bundlecaps = set()
510 self._bundlecaps = bundlecaps
517 self._bundlecaps = bundlecaps
511 # experimental config: bundle.reorder
518 # experimental config: bundle.reorder
512 reorder = repo.ui.config('bundle', 'reorder')
519 reorder = repo.ui.config('bundle', 'reorder')
513 if reorder == 'auto':
520 if reorder == 'auto':
514 reorder = None
521 reorder = None
515 else:
522 else:
516 reorder = stringutil.parsebool(reorder)
523 reorder = stringutil.parsebool(reorder)
517 self._repo = repo
524 self._repo = repo
518 self._reorder = reorder
525 self._reorder = reorder
519 if self._repo.ui.verbose and not self._repo.ui.debugflag:
526 if self._repo.ui.verbose and not self._repo.ui.debugflag:
520 self._verbosenote = self._repo.ui.note
527 self._verbosenote = self._repo.ui.note
521 else:
528 else:
522 self._verbosenote = lambda s: None
529 self._verbosenote = lambda s: None
523
530
524 def close(self):
531 def close(self):
525 return closechunk()
532 return closechunk()
526
533
527 def fileheader(self, fname):
534 def fileheader(self, fname):
528 return chunkheader(len(fname)) + fname
535 return chunkheader(len(fname)) + fname
529
536
530 # Extracted both for clarity and for overriding in extensions.
537 # Extracted both for clarity and for overriding in extensions.
531 def _sortgroup(self, revlog, nodelist, lookup):
538 def _sortgroup(self, revlog, nodelist, lookup):
532 """Sort nodes for change group and turn them into revnums."""
539 """Sort nodes for change group and turn them into revnums."""
533 # for generaldelta revlogs, we linearize the revs; this will both be
540 # for generaldelta revlogs, we linearize the revs; this will both be
534 # much quicker and generate a much smaller bundle
541 # much quicker and generate a much smaller bundle
535 if (revlog._generaldelta and self._reorder is None) or self._reorder:
542 if (revlog._generaldelta and self._reorder is None) or self._reorder:
536 dag = dagutil.revlogdag(revlog)
543 dag = dagutil.revlogdag(revlog)
537 return dag.linearize(set(revlog.rev(n) for n in nodelist))
544 return dag.linearize(set(revlog.rev(n) for n in nodelist))
538 else:
545 else:
539 return sorted([revlog.rev(n) for n in nodelist])
546 return sorted([revlog.rev(n) for n in nodelist])
540
547
541 def group(self, nodelist, revlog, lookup, units=None):
548 def group(self, nodelist, revlog, lookup, units=None):
542 """Calculate a delta group, yielding a sequence of changegroup chunks
549 """Calculate a delta group, yielding a sequence of changegroup chunks
543 (strings).
550 (strings).
544
551
545 Given a list of changeset revs, return a set of deltas and
552 Given a list of changeset revs, return a set of deltas and
546 metadata corresponding to nodes. The first delta is
553 metadata corresponding to nodes. The first delta is
547 first parent(nodelist[0]) -> nodelist[0], the receiver is
554 first parent(nodelist[0]) -> nodelist[0], the receiver is
548 guaranteed to have this parent as it has all history before
555 guaranteed to have this parent as it has all history before
549 these changesets. In the case firstparent is nullrev the
556 these changesets. In the case firstparent is nullrev the
550 changegroup starts with a full revision.
557 changegroup starts with a full revision.
551
558
552 If units is not None, progress detail will be generated, units specifies
559 If units is not None, progress detail will be generated, units specifies
553 the type of revlog that is touched (changelog, manifest, etc.).
560 the type of revlog that is touched (changelog, manifest, etc.).
554 """
561 """
555 # if we don't have any revisions touched by these changesets, bail
562 # if we don't have any revisions touched by these changesets, bail
556 if len(nodelist) == 0:
563 if len(nodelist) == 0:
557 yield self.close()
564 yield self.close()
558 return
565 return
559
566
560 revs = self._sortgroup(revlog, nodelist, lookup)
567 revs = self._sortgroup(revlog, nodelist, lookup)
561
568
562 # add the parent of the first rev
569 # add the parent of the first rev
563 p = revlog.parentrevs(revs[0])[0]
570 p = revlog.parentrevs(revs[0])[0]
564 revs.insert(0, p)
571 revs.insert(0, p)
565
572
566 # build deltas
573 # build deltas
567 progress = None
574 progress = None
568 if units is not None:
575 if units is not None:
569 progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
576 progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
570 total=(len(revs) - 1))
577 total=(len(revs) - 1))
571 for r in pycompat.xrange(len(revs) - 1):
578 for r in pycompat.xrange(len(revs) - 1):
572 if progress:
579 if progress:
573 progress.update(r + 1)
580 progress.update(r + 1)
574 prev, curr = revs[r], revs[r + 1]
581 prev, curr = revs[r], revs[r + 1]
575 linknode = lookup(revlog.node(curr))
582 linknode = lookup(revlog.node(curr))
576 for c in self.revchunk(revlog, curr, prev, linknode):
583 for c in self.revchunk(revlog, curr, prev, linknode):
577 yield c
584 yield c
578
585
579 if progress:
586 if progress:
580 progress.complete()
587 progress.complete()
581 yield self.close()
588 yield self.close()
582
589
583 # filter any nodes that claim to be part of the known set
590 # filter any nodes that claim to be part of the known set
584 def prune(self, revlog, missing, commonrevs):
591 def prune(self, revlog, missing, commonrevs):
585 rr, rl = revlog.rev, revlog.linkrev
592 rr, rl = revlog.rev, revlog.linkrev
586 return [n for n in missing if rl(rr(n)) not in commonrevs]
593 return [n for n in missing if rl(rr(n)) not in commonrevs]
587
594
588 def _packmanifests(self, dir, mfnodes, lookuplinknode):
595 def _packmanifests(self, dir, mfnodes, lookuplinknode):
589 """Pack flat manifests into a changegroup stream."""
596 """Pack flat manifests into a changegroup stream."""
590 assert not dir
597 assert not dir
591 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
598 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
592 lookuplinknode, units=_('manifests')):
599 lookuplinknode, units=_('manifests')):
593 yield chunk
600 yield chunk
594
601
595 def _manifestsdone(self):
602 def _manifestsdone(self):
596 return ''
603 return ''
597
604
598 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
605 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
599 '''yield a sequence of changegroup chunks (strings)'''
606 '''yield a sequence of changegroup chunks (strings)'''
600 repo = self._repo
607 repo = self._repo
601 cl = repo.changelog
608 cl = repo.changelog
602
609
603 clrevorder = {}
610 clrevorder = {}
604 mfs = {} # needed manifests
611 mfs = {} # needed manifests
605 fnodes = {} # needed file nodes
612 fnodes = {} # needed file nodes
606 changedfiles = set()
613 changedfiles = set()
607
614
608 # Callback for the changelog, used to collect changed files and manifest
615 # Callback for the changelog, used to collect changed files and manifest
609 # nodes.
616 # nodes.
610 # Returns the linkrev node (identity in the changelog case).
617 # Returns the linkrev node (identity in the changelog case).
611 def lookupcl(x):
618 def lookupcl(x):
612 c = cl.read(x)
619 c = cl.read(x)
613 clrevorder[x] = len(clrevorder)
620 clrevorder[x] = len(clrevorder)
614 n = c[0]
621 n = c[0]
615 # record the first changeset introducing this manifest version
622 # record the first changeset introducing this manifest version
616 mfs.setdefault(n, x)
623 mfs.setdefault(n, x)
617 # Record a complete list of potentially-changed files in
624 # Record a complete list of potentially-changed files in
618 # this manifest.
625 # this manifest.
619 changedfiles.update(c[3])
626 changedfiles.update(c[3])
620 return x
627 return x
621
628
622 self._verbosenote(_('uncompressed size of bundle content:\n'))
629 self._verbosenote(_('uncompressed size of bundle content:\n'))
623 size = 0
630 size = 0
624 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
631 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
625 size += len(chunk)
632 size += len(chunk)
626 yield chunk
633 yield chunk
627 self._verbosenote(_('%8.i (changelog)\n') % size)
634 self._verbosenote(_('%8.i (changelog)\n') % size)
628
635
629 # We need to make sure that the linkrev in the changegroup refers to
636 # We need to make sure that the linkrev in the changegroup refers to
630 # the first changeset that introduced the manifest or file revision.
637 # the first changeset that introduced the manifest or file revision.
631 # The fastpath is usually safer than the slowpath, because the filelogs
638 # The fastpath is usually safer than the slowpath, because the filelogs
632 # are walked in revlog order.
639 # are walked in revlog order.
633 #
640 #
634 # When taking the slowpath with reorder=None and the manifest revlog
641 # When taking the slowpath with reorder=None and the manifest revlog
635 # uses generaldelta, the manifest may be walked in the "wrong" order.
642 # uses generaldelta, the manifest may be walked in the "wrong" order.
636 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
643 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
637 # cc0ff93d0c0c).
644 # cc0ff93d0c0c).
638 #
645 #
639 # When taking the fastpath, we are only vulnerable to reordering
646 # When taking the fastpath, we are only vulnerable to reordering
640 # of the changelog itself. The changelog never uses generaldelta, so
647 # of the changelog itself. The changelog never uses generaldelta, so
641 # it is only reordered when reorder=True. To handle this case, we
648 # it is only reordered when reorder=True. To handle this case, we
642 # simply take the slowpath, which already has the 'clrevorder' logic.
649 # simply take the slowpath, which already has the 'clrevorder' logic.
643 # This was also fixed in cc0ff93d0c0c.
650 # This was also fixed in cc0ff93d0c0c.
644 fastpathlinkrev = fastpathlinkrev and not self._reorder
651 fastpathlinkrev = fastpathlinkrev and not self._reorder
645 # Treemanifests don't work correctly with fastpathlinkrev
652 # Treemanifests don't work correctly with fastpathlinkrev
646 # either, because we don't discover which directory nodes to
653 # either, because we don't discover which directory nodes to
647 # send along with files. This could probably be fixed.
654 # send along with files. This could probably be fixed.
648 fastpathlinkrev = fastpathlinkrev and (
655 fastpathlinkrev = fastpathlinkrev and (
649 'treemanifest' not in repo.requirements)
656 'treemanifest' not in repo.requirements)
650
657
651 for chunk in self.generatemanifests(commonrevs, clrevorder,
658 for chunk in self.generatemanifests(commonrevs, clrevorder,
652 fastpathlinkrev, mfs, fnodes, source):
659 fastpathlinkrev, mfs, fnodes, source):
653 yield chunk
660 yield chunk
654 mfs.clear()
661 mfs.clear()
655 clrevs = set(cl.rev(x) for x in clnodes)
662 clrevs = set(cl.rev(x) for x in clnodes)
656
663
657 if not fastpathlinkrev:
664 if not fastpathlinkrev:
658 def linknodes(unused, fname):
665 def linknodes(unused, fname):
659 return fnodes.get(fname, {})
666 return fnodes.get(fname, {})
660 else:
667 else:
661 cln = cl.node
668 cln = cl.node
662 def linknodes(filerevlog, fname):
669 def linknodes(filerevlog, fname):
663 llr = filerevlog.linkrev
670 llr = filerevlog.linkrev
664 fln = filerevlog.node
671 fln = filerevlog.node
665 revs = ((r, llr(r)) for r in filerevlog)
672 revs = ((r, llr(r)) for r in filerevlog)
666 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
673 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
667
674
668 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
675 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
669 source):
676 source):
670 yield chunk
677 yield chunk
671
678
672 yield self.close()
679 yield self.close()
673
680
674 if clnodes:
681 if clnodes:
675 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
682 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
676
683
677 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
684 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
678 fnodes, source):
685 fnodes, source):
679 """Returns an iterator of changegroup chunks containing manifests.
686 """Returns an iterator of changegroup chunks containing manifests.
680
687
681 `source` is unused here, but is used by extensions like remotefilelog to
688 `source` is unused here, but is used by extensions like remotefilelog to
682 change what is sent based in pulls vs pushes, etc.
689 change what is sent based in pulls vs pushes, etc.
683 """
690 """
684 repo = self._repo
691 repo = self._repo
685 mfl = repo.manifestlog
692 mfl = repo.manifestlog
686 dirlog = mfl._revlog.dirlog
693 dirlog = mfl._revlog.dirlog
687 tmfnodes = {'': mfs}
694 tmfnodes = {'': mfs}
688
695
689 # Callback for the manifest, used to collect linkrevs for filelog
696 # Callback for the manifest, used to collect linkrevs for filelog
690 # revisions.
697 # revisions.
691 # Returns the linkrev node (collected in lookupcl).
698 # Returns the linkrev node (collected in lookupcl).
692 def makelookupmflinknode(dir, nodes):
699 def makelookupmflinknode(dir, nodes):
693 if fastpathlinkrev:
700 if fastpathlinkrev:
694 assert not dir
701 assert not dir
695 return mfs.__getitem__
702 return mfs.__getitem__
696
703
697 def lookupmflinknode(x):
704 def lookupmflinknode(x):
698 """Callback for looking up the linknode for manifests.
705 """Callback for looking up the linknode for manifests.
699
706
700 Returns the linkrev node for the specified manifest.
707 Returns the linkrev node for the specified manifest.
701
708
702 SIDE EFFECT:
709 SIDE EFFECT:
703
710
704 1) fclnodes gets populated with the list of relevant
711 1) fclnodes gets populated with the list of relevant
705 file nodes if we're not using fastpathlinkrev
712 file nodes if we're not using fastpathlinkrev
706 2) When treemanifests are in use, collects treemanifest nodes
713 2) When treemanifests are in use, collects treemanifest nodes
707 to send
714 to send
708
715
709 Note that this means manifests must be completely sent to
716 Note that this means manifests must be completely sent to
710 the client before you can trust the list of files and
717 the client before you can trust the list of files and
711 treemanifests to send.
718 treemanifests to send.
712 """
719 """
713 clnode = nodes[x]
720 clnode = nodes[x]
714 mdata = mfl.get(dir, x).readfast(shallow=True)
721 mdata = mfl.get(dir, x).readfast(shallow=True)
715 for p, n, fl in mdata.iterentries():
722 for p, n, fl in mdata.iterentries():
716 if fl == 't': # subdirectory manifest
723 if fl == 't': # subdirectory manifest
717 subdir = dir + p + '/'
724 subdir = dir + p + '/'
718 tmfclnodes = tmfnodes.setdefault(subdir, {})
725 tmfclnodes = tmfnodes.setdefault(subdir, {})
719 tmfclnode = tmfclnodes.setdefault(n, clnode)
726 tmfclnode = tmfclnodes.setdefault(n, clnode)
720 if clrevorder[clnode] < clrevorder[tmfclnode]:
727 if clrevorder[clnode] < clrevorder[tmfclnode]:
721 tmfclnodes[n] = clnode
728 tmfclnodes[n] = clnode
722 else:
729 else:
723 f = dir + p
730 f = dir + p
724 fclnodes = fnodes.setdefault(f, {})
731 fclnodes = fnodes.setdefault(f, {})
725 fclnode = fclnodes.setdefault(n, clnode)
732 fclnode = fclnodes.setdefault(n, clnode)
726 if clrevorder[clnode] < clrevorder[fclnode]:
733 if clrevorder[clnode] < clrevorder[fclnode]:
727 fclnodes[n] = clnode
734 fclnodes[n] = clnode
728 return clnode
735 return clnode
729 return lookupmflinknode
736 return lookupmflinknode
730
737
731 size = 0
738 size = 0
732 while tmfnodes:
739 while tmfnodes:
733 dir, nodes = tmfnodes.popitem()
740 dir, nodes = tmfnodes.popitem()
734 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
741 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
735 if not dir or prunednodes:
742 if not dir or prunednodes:
736 for x in self._packmanifests(dir, prunednodes,
743 for x in self._packmanifests(dir, prunednodes,
737 makelookupmflinknode(dir, nodes)):
744 makelookupmflinknode(dir, nodes)):
738 size += len(x)
745 size += len(x)
739 yield x
746 yield x
740 self._verbosenote(_('%8.i (manifests)\n') % size)
747 self._verbosenote(_('%8.i (manifests)\n') % size)
741 yield self._manifestsdone()
748 yield self._manifestsdone()
742
749
743 # The 'source' parameter is useful for extensions
750 # The 'source' parameter is useful for extensions
744 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
751 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
745 repo = self._repo
752 repo = self._repo
746 progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
753 progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
747 total=len(changedfiles))
754 total=len(changedfiles))
748 for i, fname in enumerate(sorted(changedfiles)):
755 for i, fname in enumerate(sorted(changedfiles)):
749 filerevlog = repo.file(fname)
756 filerevlog = repo.file(fname)
750 if not filerevlog:
757 if not filerevlog:
751 raise error.Abort(_("empty or missing file data for %s") %
758 raise error.Abort(_("empty or missing file data for %s") %
752 fname)
759 fname)
753
760
754 linkrevnodes = linknodes(filerevlog, fname)
761 linkrevnodes = linknodes(filerevlog, fname)
755 # Lookup for filenodes, we collected the linkrev nodes above in the
762 # Lookup for filenodes, we collected the linkrev nodes above in the
756 # fastpath case and with lookupmf in the slowpath case.
763 # fastpath case and with lookupmf in the slowpath case.
757 def lookupfilelog(x):
764 def lookupfilelog(x):
758 return linkrevnodes[x]
765 return linkrevnodes[x]
759
766
760 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
767 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
761 if filenodes:
768 if filenodes:
762 progress.update(i + 1, item=fname)
769 progress.update(i + 1, item=fname)
763 h = self.fileheader(fname)
770 h = self.fileheader(fname)
764 size = len(h)
771 size = len(h)
765 yield h
772 yield h
766 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
773 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
767 size += len(chunk)
774 size += len(chunk)
768 yield chunk
775 yield chunk
769 self._verbosenote(_('%8.i %s\n') % (size, fname))
776 self._verbosenote(_('%8.i %s\n') % (size, fname))
770 progress.complete()
777 progress.complete()
771
778
772 def deltaparent(self, revlog, rev, p1, p2, prev):
779 def deltaparent(self, revlog, rev, p1, p2, prev):
773 if not revlog.candelta(prev, rev):
780 if not revlog.candelta(prev, rev):
774 raise error.ProgrammingError('cg1 should not be used in this case')
781 raise error.ProgrammingError('cg1 should not be used in this case')
775 return prev
782 return prev
776
783
777 def revchunk(self, revlog, rev, prev, linknode):
784 def revchunk(self, revlog, rev, prev, linknode):
778 node = revlog.node(rev)
785 node = revlog.node(rev)
779 p1, p2 = revlog.parentrevs(rev)
786 p1, p2 = revlog.parentrevs(rev)
780 base = self.deltaparent(revlog, rev, p1, p2, prev)
787 base = self.deltaparent(revlog, rev, p1, p2, prev)
781
788
782 prefix = ''
789 prefix = ''
783 if revlog.iscensored(base) or revlog.iscensored(rev):
790 if revlog.iscensored(base) or revlog.iscensored(rev):
784 try:
791 try:
785 delta = revlog.revision(node, raw=True)
792 delta = revlog.revision(node, raw=True)
786 except error.CensoredNodeError as e:
793 except error.CensoredNodeError as e:
787 delta = e.tombstone
794 delta = e.tombstone
788 if base == nullrev:
795 if base == nullrev:
789 prefix = mdiff.trivialdiffheader(len(delta))
796 prefix = mdiff.trivialdiffheader(len(delta))
790 else:
797 else:
791 baselen = revlog.rawsize(base)
798 baselen = revlog.rawsize(base)
792 prefix = mdiff.replacediffheader(baselen, len(delta))
799 prefix = mdiff.replacediffheader(baselen, len(delta))
793 elif base == nullrev:
800 elif base == nullrev:
794 delta = revlog.revision(node, raw=True)
801 delta = revlog.revision(node, raw=True)
795 prefix = mdiff.trivialdiffheader(len(delta))
802 prefix = mdiff.trivialdiffheader(len(delta))
796 else:
803 else:
797 delta = revlog.revdiff(base, rev)
804 delta = revlog.revdiff(base, rev)
798 p1n, p2n = revlog.parents(node)
805 p1n, p2n = revlog.parents(node)
799 basenode = revlog.node(base)
806 basenode = revlog.node(base)
800 flags = revlog.flags(rev)
807 flags = revlog.flags(rev)
801 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
808 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
802 meta += prefix
809 meta += prefix
803 l = len(meta) + len(delta)
810 l = len(meta) + len(delta)
804 yield chunkheader(l)
811 yield chunkheader(l)
805 yield meta
812 yield meta
806 yield delta
813 yield delta
807 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
814 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
808 # do nothing with basenode, it is implicitly the previous one in HG10
815 # do nothing with basenode, it is implicitly the previous one in HG10
809 # do nothing with flags, it is implicitly 0 for cg1 and cg2
816 # do nothing with flags, it is implicitly 0 for cg1 and cg2
810 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
817 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
811
818
812 class cg2packer(cg1packer):
819 class cg2packer(cg1packer):
813 version = '02'
820 version = '02'
814 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
821 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
815
822
816 def __init__(self, repo, bundlecaps=None):
823 def __init__(self, repo, filematcher, bundlecaps=None):
817 super(cg2packer, self).__init__(repo, bundlecaps)
824 super(cg2packer, self).__init__(repo, filematcher,
825 bundlecaps=bundlecaps)
826
818 if self._reorder is None:
827 if self._reorder is None:
819 # Since generaldelta is directly supported by cg2, reordering
828 # Since generaldelta is directly supported by cg2, reordering
820 # generally doesn't help, so we disable it by default (treating
829 # generally doesn't help, so we disable it by default (treating
821 # bundle.reorder=auto just like bundle.reorder=False).
830 # bundle.reorder=auto just like bundle.reorder=False).
822 self._reorder = False
831 self._reorder = False
823
832
824 def deltaparent(self, revlog, rev, p1, p2, prev):
833 def deltaparent(self, revlog, rev, p1, p2, prev):
825 dp = revlog.deltaparent(rev)
834 dp = revlog.deltaparent(rev)
826 if dp == nullrev and revlog.storedeltachains:
835 if dp == nullrev and revlog.storedeltachains:
827 # Avoid sending full revisions when delta parent is null. Pick prev
836 # Avoid sending full revisions when delta parent is null. Pick prev
828 # in that case. It's tempting to pick p1 in this case, as p1 will
837 # in that case. It's tempting to pick p1 in this case, as p1 will
829 # be smaller in the common case. However, computing a delta against
838 # be smaller in the common case. However, computing a delta against
830 # p1 may require resolving the raw text of p1, which could be
839 # p1 may require resolving the raw text of p1, which could be
831 # expensive. The revlog caches should have prev cached, meaning
840 # expensive. The revlog caches should have prev cached, meaning
832 # less CPU for changegroup generation. There is likely room to add
841 # less CPU for changegroup generation. There is likely room to add
833 # a flag and/or config option to control this behavior.
842 # a flag and/or config option to control this behavior.
834 base = prev
843 base = prev
835 elif dp == nullrev:
844 elif dp == nullrev:
836 # revlog is configured to use full snapshot for a reason,
845 # revlog is configured to use full snapshot for a reason,
837 # stick to full snapshot.
846 # stick to full snapshot.
838 base = nullrev
847 base = nullrev
839 elif dp not in (p1, p2, prev):
848 elif dp not in (p1, p2, prev):
840 # Pick prev when we can't be sure remote has the base revision.
849 # Pick prev when we can't be sure remote has the base revision.
841 return prev
850 return prev
842 else:
851 else:
843 base = dp
852 base = dp
844 if base != nullrev and not revlog.candelta(base, rev):
853 if base != nullrev and not revlog.candelta(base, rev):
845 base = nullrev
854 base = nullrev
846 return base
855 return base
847
856
848 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
857 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
849 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
858 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
850 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
859 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
851
860
852 class cg3packer(cg2packer):
861 class cg3packer(cg2packer):
853 version = '03'
862 version = '03'
854 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
863 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
855
864
856 def _packmanifests(self, dir, mfnodes, lookuplinknode):
865 def _packmanifests(self, dir, mfnodes, lookuplinknode):
857 if dir:
866 if dir:
858 yield self.fileheader(dir)
867 yield self.fileheader(dir)
859
868
860 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
869 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
861 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
870 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
862 units=_('manifests')):
871 units=_('manifests')):
863 yield chunk
872 yield chunk
864
873
865 def _manifestsdone(self):
874 def _manifestsdone(self):
866 return self.close()
875 return self.close()
867
876
868 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
877 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
869 return struct.pack(
878 return struct.pack(
870 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
879 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
871
880
872 _packermap = {'01': (cg1packer, cg1unpacker),
881 _packermap = {'01': (cg1packer, cg1unpacker),
873 # cg2 adds support for exchanging generaldelta
882 # cg2 adds support for exchanging generaldelta
874 '02': (cg2packer, cg2unpacker),
883 '02': (cg2packer, cg2unpacker),
875 # cg3 adds support for exchanging revlog flags and treemanifests
884 # cg3 adds support for exchanging revlog flags and treemanifests
876 '03': (cg3packer, cg3unpacker),
885 '03': (cg3packer, cg3unpacker),
877 }
886 }
878
887
879 def allsupportedversions(repo):
888 def allsupportedversions(repo):
880 versions = set(_packermap.keys())
889 versions = set(_packermap.keys())
881 if not (repo.ui.configbool('experimental', 'changegroup3') or
890 if not (repo.ui.configbool('experimental', 'changegroup3') or
882 repo.ui.configbool('experimental', 'treemanifest') or
891 repo.ui.configbool('experimental', 'treemanifest') or
883 'treemanifest' in repo.requirements):
892 'treemanifest' in repo.requirements):
884 versions.discard('03')
893 versions.discard('03')
885 return versions
894 return versions
886
895
887 # Changegroup versions that can be applied to the repo
896 # Changegroup versions that can be applied to the repo
888 def supportedincomingversions(repo):
897 def supportedincomingversions(repo):
889 return allsupportedversions(repo)
898 return allsupportedversions(repo)
890
899
891 # Changegroup versions that can be created from the repo
900 # Changegroup versions that can be created from the repo
892 def supportedoutgoingversions(repo):
901 def supportedoutgoingversions(repo):
893 versions = allsupportedversions(repo)
902 versions = allsupportedversions(repo)
894 if 'treemanifest' in repo.requirements:
903 if 'treemanifest' in repo.requirements:
895 # Versions 01 and 02 support only flat manifests and it's just too
904 # Versions 01 and 02 support only flat manifests and it's just too
896 # expensive to convert between the flat manifest and tree manifest on
905 # expensive to convert between the flat manifest and tree manifest on
897 # the fly. Since tree manifests are hashed differently, all of history
906 # the fly. Since tree manifests are hashed differently, all of history
898 # would have to be converted. Instead, we simply don't even pretend to
907 # would have to be converted. Instead, we simply don't even pretend to
899 # support versions 01 and 02.
908 # support versions 01 and 02.
900 versions.discard('01')
909 versions.discard('01')
901 versions.discard('02')
910 versions.discard('02')
902 if NARROW_REQUIREMENT in repo.requirements:
911 if NARROW_REQUIREMENT in repo.requirements:
903 # Versions 01 and 02 don't support revlog flags, and we need to
912 # Versions 01 and 02 don't support revlog flags, and we need to
904 # support that for stripping and unbundling to work.
913 # support that for stripping and unbundling to work.
905 versions.discard('01')
914 versions.discard('01')
906 versions.discard('02')
915 versions.discard('02')
907 if LFS_REQUIREMENT in repo.requirements:
916 if LFS_REQUIREMENT in repo.requirements:
908 # Versions 01 and 02 don't support revlog flags, and we need to
917 # Versions 01 and 02 don't support revlog flags, and we need to
909 # mark LFS entries with REVIDX_EXTSTORED.
918 # mark LFS entries with REVIDX_EXTSTORED.
910 versions.discard('01')
919 versions.discard('01')
911 versions.discard('02')
920 versions.discard('02')
912
921
913 return versions
922 return versions
914
923
915 def localversion(repo):
924 def localversion(repo):
916 # Finds the best version to use for bundles that are meant to be used
925 # Finds the best version to use for bundles that are meant to be used
917 # locally, such as those from strip and shelve, and temporary bundles.
926 # locally, such as those from strip and shelve, and temporary bundles.
918 return max(supportedoutgoingversions(repo))
927 return max(supportedoutgoingversions(repo))
919
928
920 def safeversion(repo):
929 def safeversion(repo):
921 # Finds the smallest version that it's safe to assume clients of the repo
930 # Finds the smallest version that it's safe to assume clients of the repo
922 # will support. For example, all hg versions that support generaldelta also
931 # will support. For example, all hg versions that support generaldelta also
923 # support changegroup 02.
932 # support changegroup 02.
924 versions = supportedoutgoingversions(repo)
933 versions = supportedoutgoingversions(repo)
925 if 'generaldelta' in repo.requirements:
934 if 'generaldelta' in repo.requirements:
926 versions.discard('01')
935 versions.discard('01')
927 assert versions
936 assert versions
928 return min(versions)
937 return min(versions)
929
938
930 def getbundler(version, repo, bundlecaps=None):
939 def getbundler(version, repo, bundlecaps=None, filematcher=None):
931 assert version in supportedoutgoingversions(repo)
940 assert version in supportedoutgoingversions(repo)
932 return _packermap[version][0](repo, bundlecaps)
941
942 if filematcher is None:
943 filematcher = matchmod.alwaysmatcher(repo.root, '')
944
945 if version == '01' and not filematcher.always():
946 raise error.ProgrammingError('version 01 changegroups do not support '
947 'sparse file matchers')
948
949 # Requested files could include files not in the local store. So
950 # filter those out.
951 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
952 filematcher)
953
954 return _packermap[version][0](repo, filematcher=filematcher,
955 bundlecaps=bundlecaps)
933
956
934 def getunbundler(version, fh, alg, extras=None):
957 def getunbundler(version, fh, alg, extras=None):
935 return _packermap[version][1](fh, alg, extras=extras)
958 return _packermap[version][1](fh, alg, extras=extras)
936
959
937 def _changegroupinfo(repo, nodes, source):
960 def _changegroupinfo(repo, nodes, source):
938 if repo.ui.verbose or source == 'bundle':
961 if repo.ui.verbose or source == 'bundle':
939 repo.ui.status(_("%d changesets found\n") % len(nodes))
962 repo.ui.status(_("%d changesets found\n") % len(nodes))
940 if repo.ui.debugflag:
963 if repo.ui.debugflag:
941 repo.ui.debug("list of changesets:\n")
964 repo.ui.debug("list of changesets:\n")
942 for node in nodes:
965 for node in nodes:
943 repo.ui.debug("%s\n" % hex(node))
966 repo.ui.debug("%s\n" % hex(node))
944
967
945 def makechangegroup(repo, outgoing, version, source, fastpath=False,
968 def makechangegroup(repo, outgoing, version, source, fastpath=False,
946 bundlecaps=None):
969 bundlecaps=None):
947 cgstream = makestream(repo, outgoing, version, source,
970 cgstream = makestream(repo, outgoing, version, source,
948 fastpath=fastpath, bundlecaps=bundlecaps)
971 fastpath=fastpath, bundlecaps=bundlecaps)
949 return getunbundler(version, util.chunkbuffer(cgstream), None,
972 return getunbundler(version, util.chunkbuffer(cgstream), None,
950 {'clcount': len(outgoing.missing) })
973 {'clcount': len(outgoing.missing) })
951
974
952 def makestream(repo, outgoing, version, source, fastpath=False,
975 def makestream(repo, outgoing, version, source, fastpath=False,
953 bundlecaps=None):
976 bundlecaps=None, filematcher=None):
954 bundler = getbundler(version, repo, bundlecaps=bundlecaps)
977 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
978 filematcher=filematcher)
955
979
956 repo = repo.unfiltered()
980 repo = repo.unfiltered()
957 commonrevs = outgoing.common
981 commonrevs = outgoing.common
958 csets = outgoing.missing
982 csets = outgoing.missing
959 heads = outgoing.missingheads
983 heads = outgoing.missingheads
960 # We go through the fast path if we get told to, or if all (unfiltered
984 # We go through the fast path if we get told to, or if all (unfiltered
961 # heads have been requested (since we then know there all linkrevs will
985 # heads have been requested (since we then know there all linkrevs will
962 # be pulled by the client).
986 # be pulled by the client).
963 heads.sort()
987 heads.sort()
964 fastpathlinkrev = fastpath or (
988 fastpathlinkrev = fastpath or (
965 repo.filtername is None and heads == sorted(repo.heads()))
989 repo.filtername is None and heads == sorted(repo.heads()))
966
990
967 repo.hook('preoutgoing', throw=True, source=source)
991 repo.hook('preoutgoing', throw=True, source=source)
968 _changegroupinfo(repo, csets, source)
992 _changegroupinfo(repo, csets, source)
969 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
993 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
970
994
971 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
995 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
972 revisions = 0
996 revisions = 0
973 files = 0
997 files = 0
974 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
998 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
975 total=expectedfiles)
999 total=expectedfiles)
976 for chunkdata in iter(source.filelogheader, {}):
1000 for chunkdata in iter(source.filelogheader, {}):
977 files += 1
1001 files += 1
978 f = chunkdata["filename"]
1002 f = chunkdata["filename"]
979 repo.ui.debug("adding %s revisions\n" % f)
1003 repo.ui.debug("adding %s revisions\n" % f)
980 progress.increment()
1004 progress.increment()
981 fl = repo.file(f)
1005 fl = repo.file(f)
982 o = len(fl)
1006 o = len(fl)
983 try:
1007 try:
984 deltas = source.deltaiter()
1008 deltas = source.deltaiter()
985 if not fl.addgroup(deltas, revmap, trp):
1009 if not fl.addgroup(deltas, revmap, trp):
986 raise error.Abort(_("received file revlog group is empty"))
1010 raise error.Abort(_("received file revlog group is empty"))
987 except error.CensoredBaseError as e:
1011 except error.CensoredBaseError as e:
988 raise error.Abort(_("received delta base is censored: %s") % e)
1012 raise error.Abort(_("received delta base is censored: %s") % e)
989 revisions += len(fl) - o
1013 revisions += len(fl) - o
990 if f in needfiles:
1014 if f in needfiles:
991 needs = needfiles[f]
1015 needs = needfiles[f]
992 for new in pycompat.xrange(o, len(fl)):
1016 for new in pycompat.xrange(o, len(fl)):
993 n = fl.node(new)
1017 n = fl.node(new)
994 if n in needs:
1018 if n in needs:
995 needs.remove(n)
1019 needs.remove(n)
996 else:
1020 else:
997 raise error.Abort(
1021 raise error.Abort(
998 _("received spurious file revlog entry"))
1022 _("received spurious file revlog entry"))
999 if not needs:
1023 if not needs:
1000 del needfiles[f]
1024 del needfiles[f]
1001 progress.complete()
1025 progress.complete()
1002
1026
1003 for f, needs in needfiles.iteritems():
1027 for f, needs in needfiles.iteritems():
1004 fl = repo.file(f)
1028 fl = repo.file(f)
1005 for n in needs:
1029 for n in needs:
1006 try:
1030 try:
1007 fl.rev(n)
1031 fl.rev(n)
1008 except error.LookupError:
1032 except error.LookupError:
1009 raise error.Abort(
1033 raise error.Abort(
1010 _('missing file data for %s:%s - run hg verify') %
1034 _('missing file data for %s:%s - run hg verify') %
1011 (f, hex(n)))
1035 (f, hex(n)))
1012
1036
1013 return revisions, files
1037 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now