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