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