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