##// END OF EJS Templates
narrow: consider both local and remote matchers in narrowchangegroup...
Martin von Zweigbergk -
r36485:2d82a24d default
parent child Browse files
Show More
@@ -1,366 +1,372
1 # narrowchangegroup.py - narrow clone changegroup creation and consumption
1 # narrowchangegroup.py - narrow clone changegroup creation and consumption
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import (
11 from mercurial import (
12 changegroup,
12 changegroup,
13 error,
13 error,
14 extensions,
14 extensions,
15 manifest,
15 manifest,
16 match as matchmod,
16 mdiff,
17 mdiff,
17 node,
18 node,
18 revlog,
19 revlog,
19 util,
20 util,
20 )
21 )
21
22
22 def setup():
23 def setup():
23
24
25 def _cgmatcher(cgpacker):
26 localmatcher = getattr(cgpacker._repo, 'narrowmatch', lambda: None)()
27 remotematcher = getattr(cgpacker, '_narrow_matcher', lambda: None)()
28 if localmatcher and remotematcher:
29 return matchmod.intersectmatchers(localmatcher, remotematcher)
30 else:
31 return localmatcher or remotematcher
32
24 def prune(orig, self, revlog, missing, commonrevs):
33 def prune(orig, self, revlog, missing, commonrevs):
25 if isinstance(revlog, manifest.manifestrevlog):
34 if isinstance(revlog, manifest.manifestrevlog):
26 matcher = getattr(self._repo, 'narrowmatch',
35 matcher = _cgmatcher(self)
27 getattr(self, '_narrow_matcher', None))
36 if (matcher and
28 if (matcher is not None and
37 not matcher.visitdir(revlog._dir[:-1] or '.')):
29 not matcher().visitdir(revlog._dir[:-1] or '.')):
30 return []
38 return []
31 return orig(self, revlog, missing, commonrevs)
39 return orig(self, revlog, missing, commonrevs)
32
40
33 extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
41 extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
34
42
35 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
43 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
36 source):
44 source):
37 matcher = getattr(self._repo, 'narrowmatch',
45 matcher = _cgmatcher(self)
38 getattr(self, '_narrow_matcher', None))
46 if matcher:
39 if matcher is not None:
47 changedfiles = filter(matcher, changedfiles)
40 narrowmatch = matcher()
41 changedfiles = [f for f in changedfiles if narrowmatch(f)]
42 if getattr(self, 'is_shallow', False):
48 if getattr(self, 'is_shallow', False):
43 # See comment in generate() for why this sadness is a thing.
49 # See comment in generate() for why this sadness is a thing.
44 mfdicts = self._mfdicts
50 mfdicts = self._mfdicts
45 del self._mfdicts
51 del self._mfdicts
46 # In a shallow clone, the linknodes callback needs to also include
52 # In a shallow clone, the linknodes callback needs to also include
47 # those file nodes that are in the manifests we sent but weren't
53 # those file nodes that are in the manifests we sent but weren't
48 # introduced by those manifests.
54 # introduced by those manifests.
49 commonctxs = [self._repo[c] for c in commonrevs]
55 commonctxs = [self._repo[c] for c in commonrevs]
50 oldlinknodes = linknodes
56 oldlinknodes = linknodes
51 clrev = self._repo.changelog.rev
57 clrev = self._repo.changelog.rev
52 def linknodes(flog, fname):
58 def linknodes(flog, fname):
53 for c in commonctxs:
59 for c in commonctxs:
54 try:
60 try:
55 fnode = c.filenode(fname)
61 fnode = c.filenode(fname)
56 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
62 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
57 except error.ManifestLookupError:
63 except error.ManifestLookupError:
58 pass
64 pass
59 links = oldlinknodes(flog, fname)
65 links = oldlinknodes(flog, fname)
60 if len(links) != len(mfdicts):
66 if len(links) != len(mfdicts):
61 for mf, lr in mfdicts:
67 for mf, lr in mfdicts:
62 fnode = mf.get(fname, None)
68 fnode = mf.get(fname, None)
63 if fnode in links:
69 if fnode in links:
64 links[fnode] = min(links[fnode], lr, key=clrev)
70 links[fnode] = min(links[fnode], lr, key=clrev)
65 elif fnode:
71 elif fnode:
66 links[fnode] = lr
72 links[fnode] = lr
67 return links
73 return links
68 return orig(self, changedfiles, linknodes, commonrevs, source)
74 return orig(self, changedfiles, linknodes, commonrevs, source)
69 extensions.wrapfunction(
75 extensions.wrapfunction(
70 changegroup.cg1packer, 'generatefiles', generatefiles)
76 changegroup.cg1packer, 'generatefiles', generatefiles)
71
77
72 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
78 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
73 n = revlog_.node(rev)
79 n = revlog_.node(rev)
74 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
80 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
75 flags = revlog_.flags(rev)
81 flags = revlog_.flags(rev)
76 flags |= revlog.REVIDX_ELLIPSIS
82 flags |= revlog.REVIDX_ELLIPSIS
77 meta = packer.builddeltaheader(
83 meta = packer.builddeltaheader(
78 n, p1n, p2n, node.nullid, linknode, flags)
84 n, p1n, p2n, node.nullid, linknode, flags)
79 # TODO: try and actually send deltas for ellipsis data blocks
85 # TODO: try and actually send deltas for ellipsis data blocks
80 diffheader = mdiff.trivialdiffheader(len(data))
86 diffheader = mdiff.trivialdiffheader(len(data))
81 l = len(meta) + len(diffheader) + len(data)
87 l = len(meta) + len(diffheader) + len(data)
82 return ''.join((changegroup.chunkheader(l),
88 return ''.join((changegroup.chunkheader(l),
83 meta,
89 meta,
84 diffheader,
90 diffheader,
85 data))
91 data))
86
92
87 def close(orig, self):
93 def close(orig, self):
88 getattr(self, 'clrev_to_localrev', {}).clear()
94 getattr(self, 'clrev_to_localrev', {}).clear()
89 if getattr(self, 'next_clrev_to_localrev', {}):
95 if getattr(self, 'next_clrev_to_localrev', {}):
90 self.clrev_to_localrev = self.next_clrev_to_localrev
96 self.clrev_to_localrev = self.next_clrev_to_localrev
91 del self.next_clrev_to_localrev
97 del self.next_clrev_to_localrev
92 self.changelog_done = True
98 self.changelog_done = True
93 return orig(self)
99 return orig(self)
94 extensions.wrapfunction(changegroup.cg1packer, 'close', close)
100 extensions.wrapfunction(changegroup.cg1packer, 'close', close)
95
101
96 # In a perfect world, we'd generate better ellipsis-ified graphs
102 # In a perfect world, we'd generate better ellipsis-ified graphs
97 # for non-changelog revlogs. In practice, we haven't started doing
103 # for non-changelog revlogs. In practice, we haven't started doing
98 # that yet, so the resulting DAGs for the manifestlog and filelogs
104 # that yet, so the resulting DAGs for the manifestlog and filelogs
99 # are actually full of bogus parentage on all the ellipsis
105 # are actually full of bogus parentage on all the ellipsis
100 # nodes. This has the side effect that, while the contents are
106 # nodes. This has the side effect that, while the contents are
101 # correct, the individual DAGs might be completely out of whack in
107 # correct, the individual DAGs might be completely out of whack in
102 # a case like 882681bc3166 and its ancestors (back about 10
108 # a case like 882681bc3166 and its ancestors (back about 10
103 # revisions or so) in the main hg repo.
109 # revisions or so) in the main hg repo.
104 #
110 #
105 # The one invariant we *know* holds is that the new (potentially
111 # The one invariant we *know* holds is that the new (potentially
106 # bogus) DAG shape will be valid if we order the nodes in the
112 # bogus) DAG shape will be valid if we order the nodes in the
107 # order that they're introduced in dramatis personae by the
113 # order that they're introduced in dramatis personae by the
108 # changelog, so what we do is we sort the non-changelog histories
114 # changelog, so what we do is we sort the non-changelog histories
109 # by the order in which they are used by the changelog.
115 # by the order in which they are used by the changelog.
110 def _sortgroup(orig, self, revlog, nodelist, lookup):
116 def _sortgroup(orig, self, revlog, nodelist, lookup):
111 if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
117 if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
112 return orig(self, revlog, nodelist, lookup)
118 return orig(self, revlog, nodelist, lookup)
113 key = lambda n: self.clnode_to_rev[lookup(n)]
119 key = lambda n: self.clnode_to_rev[lookup(n)]
114 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
120 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
115
121
116 extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
122 extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
117
123
118 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
124 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
119 '''yield a sequence of changegroup chunks (strings)'''
125 '''yield a sequence of changegroup chunks (strings)'''
120 # Note: other than delegating to orig, the only deviation in
126 # Note: other than delegating to orig, the only deviation in
121 # logic from normal hg's generate is marked with BEGIN/END
127 # logic from normal hg's generate is marked with BEGIN/END
122 # NARROW HACK.
128 # NARROW HACK.
123 if not util.safehasattr(self, 'full_nodes'):
129 if not util.safehasattr(self, 'full_nodes'):
124 # not sending a narrow bundle
130 # not sending a narrow bundle
125 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
131 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
126 yield x
132 yield x
127 return
133 return
128
134
129 repo = self._repo
135 repo = self._repo
130 cl = repo.changelog
136 cl = repo.changelog
131 mfl = repo.manifestlog
137 mfl = repo.manifestlog
132 mfrevlog = mfl._revlog
138 mfrevlog = mfl._revlog
133
139
134 clrevorder = {}
140 clrevorder = {}
135 mfs = {} # needed manifests
141 mfs = {} # needed manifests
136 fnodes = {} # needed file nodes
142 fnodes = {} # needed file nodes
137 changedfiles = set()
143 changedfiles = set()
138
144
139 # Callback for the changelog, used to collect changed files and manifest
145 # Callback for the changelog, used to collect changed files and manifest
140 # nodes.
146 # nodes.
141 # Returns the linkrev node (identity in the changelog case).
147 # Returns the linkrev node (identity in the changelog case).
142 def lookupcl(x):
148 def lookupcl(x):
143 c = cl.read(x)
149 c = cl.read(x)
144 clrevorder[x] = len(clrevorder)
150 clrevorder[x] = len(clrevorder)
145 # BEGIN NARROW HACK
151 # BEGIN NARROW HACK
146 #
152 #
147 # Only update mfs if x is going to be sent. Otherwise we
153 # Only update mfs if x is going to be sent. Otherwise we
148 # end up with bogus linkrevs specified for manifests and
154 # end up with bogus linkrevs specified for manifests and
149 # we skip some manifest nodes that we should otherwise
155 # we skip some manifest nodes that we should otherwise
150 # have sent.
156 # have sent.
151 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
157 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
152 n = c[0]
158 n = c[0]
153 # record the first changeset introducing this manifest version
159 # record the first changeset introducing this manifest version
154 mfs.setdefault(n, x)
160 mfs.setdefault(n, x)
155 # Set this narrow-specific dict so we have the lowest manifest
161 # Set this narrow-specific dict so we have the lowest manifest
156 # revnum to look up for this cl revnum. (Part of mapping
162 # revnum to look up for this cl revnum. (Part of mapping
157 # changelog ellipsis parents to manifest ellipsis parents)
163 # changelog ellipsis parents to manifest ellipsis parents)
158 self.next_clrev_to_localrev.setdefault(cl.rev(x),
164 self.next_clrev_to_localrev.setdefault(cl.rev(x),
159 mfrevlog.rev(n))
165 mfrevlog.rev(n))
160 # We can't trust the changed files list in the changeset if the
166 # We can't trust the changed files list in the changeset if the
161 # client requested a shallow clone.
167 # client requested a shallow clone.
162 if self.is_shallow:
168 if self.is_shallow:
163 changedfiles.update(mfl[c[0]].read().keys())
169 changedfiles.update(mfl[c[0]].read().keys())
164 else:
170 else:
165 changedfiles.update(c[3])
171 changedfiles.update(c[3])
166 # END NARROW HACK
172 # END NARROW HACK
167 # Record a complete list of potentially-changed files in
173 # Record a complete list of potentially-changed files in
168 # this manifest.
174 # this manifest.
169 return x
175 return x
170
176
171 self._verbosenote(_('uncompressed size of bundle content:\n'))
177 self._verbosenote(_('uncompressed size of bundle content:\n'))
172 size = 0
178 size = 0
173 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
179 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
174 size += len(chunk)
180 size += len(chunk)
175 yield chunk
181 yield chunk
176 self._verbosenote(_('%8.i (changelog)\n') % size)
182 self._verbosenote(_('%8.i (changelog)\n') % size)
177
183
178 # We need to make sure that the linkrev in the changegroup refers to
184 # We need to make sure that the linkrev in the changegroup refers to
179 # the first changeset that introduced the manifest or file revision.
185 # the first changeset that introduced the manifest or file revision.
180 # The fastpath is usually safer than the slowpath, because the filelogs
186 # The fastpath is usually safer than the slowpath, because the filelogs
181 # are walked in revlog order.
187 # are walked in revlog order.
182 #
188 #
183 # When taking the slowpath with reorder=None and the manifest revlog
189 # When taking the slowpath with reorder=None and the manifest revlog
184 # uses generaldelta, the manifest may be walked in the "wrong" order.
190 # uses generaldelta, the manifest may be walked in the "wrong" order.
185 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
191 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
186 # cc0ff93d0c0c).
192 # cc0ff93d0c0c).
187 #
193 #
188 # When taking the fastpath, we are only vulnerable to reordering
194 # When taking the fastpath, we are only vulnerable to reordering
189 # of the changelog itself. The changelog never uses generaldelta, so
195 # of the changelog itself. The changelog never uses generaldelta, so
190 # it is only reordered when reorder=True. To handle this case, we
196 # it is only reordered when reorder=True. To handle this case, we
191 # simply take the slowpath, which already has the 'clrevorder' logic.
197 # simply take the slowpath, which already has the 'clrevorder' logic.
192 # This was also fixed in cc0ff93d0c0c.
198 # This was also fixed in cc0ff93d0c0c.
193 fastpathlinkrev = fastpathlinkrev and not self._reorder
199 fastpathlinkrev = fastpathlinkrev and not self._reorder
194 # Treemanifests don't work correctly with fastpathlinkrev
200 # Treemanifests don't work correctly with fastpathlinkrev
195 # either, because we don't discover which directory nodes to
201 # either, because we don't discover which directory nodes to
196 # send along with files. This could probably be fixed.
202 # send along with files. This could probably be fixed.
197 fastpathlinkrev = fastpathlinkrev and (
203 fastpathlinkrev = fastpathlinkrev and (
198 'treemanifest' not in repo.requirements)
204 'treemanifest' not in repo.requirements)
199 # Shallow clones also don't work correctly with fastpathlinkrev
205 # Shallow clones also don't work correctly with fastpathlinkrev
200 # because file nodes may need to be sent for a manifest even if they
206 # because file nodes may need to be sent for a manifest even if they
201 # weren't introduced by that manifest.
207 # weren't introduced by that manifest.
202 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
208 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
203
209
204 for chunk in self.generatemanifests(commonrevs, clrevorder,
210 for chunk in self.generatemanifests(commonrevs, clrevorder,
205 fastpathlinkrev, mfs, fnodes, source):
211 fastpathlinkrev, mfs, fnodes, source):
206 yield chunk
212 yield chunk
207 # BEGIN NARROW HACK
213 # BEGIN NARROW HACK
208 mfdicts = None
214 mfdicts = None
209 if self.is_shallow:
215 if self.is_shallow:
210 mfdicts = [(self._repo.manifestlog[n].read(), lr)
216 mfdicts = [(self._repo.manifestlog[n].read(), lr)
211 for (n, lr) in mfs.iteritems()]
217 for (n, lr) in mfs.iteritems()]
212 # END NARROW HACK
218 # END NARROW HACK
213 mfs.clear()
219 mfs.clear()
214 clrevs = set(cl.rev(x) for x in clnodes)
220 clrevs = set(cl.rev(x) for x in clnodes)
215
221
216 if not fastpathlinkrev:
222 if not fastpathlinkrev:
217 def linknodes(unused, fname):
223 def linknodes(unused, fname):
218 return fnodes.get(fname, {})
224 return fnodes.get(fname, {})
219 else:
225 else:
220 cln = cl.node
226 cln = cl.node
221 def linknodes(filerevlog, fname):
227 def linknodes(filerevlog, fname):
222 llr = filerevlog.linkrev
228 llr = filerevlog.linkrev
223 fln = filerevlog.node
229 fln = filerevlog.node
224 revs = ((r, llr(r)) for r in filerevlog)
230 revs = ((r, llr(r)) for r in filerevlog)
225 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
231 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
226
232
227 # BEGIN NARROW HACK
233 # BEGIN NARROW HACK
228 #
234 #
229 # We need to pass the mfdicts variable down into
235 # We need to pass the mfdicts variable down into
230 # generatefiles(), but more than one command might have
236 # generatefiles(), but more than one command might have
231 # wrapped generatefiles so we can't modify the function
237 # wrapped generatefiles so we can't modify the function
232 # signature. Instead, we pass the data to ourselves using an
238 # signature. Instead, we pass the data to ourselves using an
233 # instance attribute. I'm sorry.
239 # instance attribute. I'm sorry.
234 self._mfdicts = mfdicts
240 self._mfdicts = mfdicts
235 # END NARROW HACK
241 # END NARROW HACK
236 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
242 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
237 source):
243 source):
238 yield chunk
244 yield chunk
239
245
240 yield self.close()
246 yield self.close()
241
247
242 if clnodes:
248 if clnodes:
243 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
249 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
244 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
250 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
245
251
246 def revchunk(orig, self, revlog, rev, prev, linknode):
252 def revchunk(orig, self, revlog, rev, prev, linknode):
247 if not util.safehasattr(self, 'full_nodes'):
253 if not util.safehasattr(self, 'full_nodes'):
248 # not sending a narrow changegroup
254 # not sending a narrow changegroup
249 for x in orig(self, revlog, rev, prev, linknode):
255 for x in orig(self, revlog, rev, prev, linknode):
250 yield x
256 yield x
251 return
257 return
252 # build up some mapping information that's useful later. See
258 # build up some mapping information that's useful later. See
253 # the local() nested function below.
259 # the local() nested function below.
254 if not self.changelog_done:
260 if not self.changelog_done:
255 self.clnode_to_rev[linknode] = rev
261 self.clnode_to_rev[linknode] = rev
256 linkrev = rev
262 linkrev = rev
257 self.clrev_to_localrev[linkrev] = rev
263 self.clrev_to_localrev[linkrev] = rev
258 else:
264 else:
259 linkrev = self.clnode_to_rev[linknode]
265 linkrev = self.clnode_to_rev[linknode]
260 self.clrev_to_localrev[linkrev] = rev
266 self.clrev_to_localrev[linkrev] = rev
261 # This is a node to send in full, because the changeset it
267 # This is a node to send in full, because the changeset it
262 # corresponds to was a full changeset.
268 # corresponds to was a full changeset.
263 if linknode in self.full_nodes:
269 if linknode in self.full_nodes:
264 for x in orig(self, revlog, rev, prev, linknode):
270 for x in orig(self, revlog, rev, prev, linknode):
265 yield x
271 yield x
266 return
272 return
267 # At this point, a node can either be one we should skip or an
273 # At this point, a node can either be one we should skip or an
268 # ellipsis. If it's not an ellipsis, bail immediately.
274 # ellipsis. If it's not an ellipsis, bail immediately.
269 if linkrev not in self.precomputed_ellipsis:
275 if linkrev not in self.precomputed_ellipsis:
270 return
276 return
271 linkparents = self.precomputed_ellipsis[linkrev]
277 linkparents = self.precomputed_ellipsis[linkrev]
272 def local(clrev):
278 def local(clrev):
273 """Turn a changelog revnum into a local revnum.
279 """Turn a changelog revnum into a local revnum.
274
280
275 The ellipsis dag is stored as revnums on the changelog,
281 The ellipsis dag is stored as revnums on the changelog,
276 but when we're producing ellipsis entries for
282 but when we're producing ellipsis entries for
277 non-changelog revlogs, we need to turn those numbers into
283 non-changelog revlogs, we need to turn those numbers into
278 something local. This does that for us, and during the
284 something local. This does that for us, and during the
279 changelog sending phase will also expand the stored
285 changelog sending phase will also expand the stored
280 mappings as needed.
286 mappings as needed.
281 """
287 """
282 if clrev == node.nullrev:
288 if clrev == node.nullrev:
283 return node.nullrev
289 return node.nullrev
284 if not self.changelog_done:
290 if not self.changelog_done:
285 # If we're doing the changelog, it's possible that we
291 # If we're doing the changelog, it's possible that we
286 # have a parent that is already on the client, and we
292 # have a parent that is already on the client, and we
287 # need to store some extra mapping information so that
293 # need to store some extra mapping information so that
288 # our contained ellipsis nodes will be able to resolve
294 # our contained ellipsis nodes will be able to resolve
289 # their parents.
295 # their parents.
290 if clrev not in self.clrev_to_localrev:
296 if clrev not in self.clrev_to_localrev:
291 clnode = revlog.node(clrev)
297 clnode = revlog.node(clrev)
292 self.clnode_to_rev[clnode] = clrev
298 self.clnode_to_rev[clnode] = clrev
293 return clrev
299 return clrev
294 # Walk the ellipsis-ized changelog breadth-first looking for a
300 # Walk the ellipsis-ized changelog breadth-first looking for a
295 # change that has been linked from the current revlog.
301 # change that has been linked from the current revlog.
296 #
302 #
297 # For a flat manifest revlog only a single step should be necessary
303 # For a flat manifest revlog only a single step should be necessary
298 # as all relevant changelog entries are relevant to the flat
304 # as all relevant changelog entries are relevant to the flat
299 # manifest.
305 # manifest.
300 #
306 #
301 # For a filelog or tree manifest dirlog however not every changelog
307 # For a filelog or tree manifest dirlog however not every changelog
302 # entry will have been relevant, so we need to skip some changelog
308 # entry will have been relevant, so we need to skip some changelog
303 # nodes even after ellipsis-izing.
309 # nodes even after ellipsis-izing.
304 walk = [clrev]
310 walk = [clrev]
305 while walk:
311 while walk:
306 p = walk[0]
312 p = walk[0]
307 walk = walk[1:]
313 walk = walk[1:]
308 if p in self.clrev_to_localrev:
314 if p in self.clrev_to_localrev:
309 return self.clrev_to_localrev[p]
315 return self.clrev_to_localrev[p]
310 elif p in self.full_nodes:
316 elif p in self.full_nodes:
311 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
317 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
312 if pp != node.nullrev])
318 if pp != node.nullrev])
313 elif p in self.precomputed_ellipsis:
319 elif p in self.precomputed_ellipsis:
314 walk.extend([pp for pp in self.precomputed_ellipsis[p]
320 walk.extend([pp for pp in self.precomputed_ellipsis[p]
315 if pp != node.nullrev])
321 if pp != node.nullrev])
316 else:
322 else:
317 # In this case, we've got an ellipsis with parents
323 # In this case, we've got an ellipsis with parents
318 # outside the current bundle (likely an
324 # outside the current bundle (likely an
319 # incremental pull). We "know" that we can use the
325 # incremental pull). We "know" that we can use the
320 # value of this same revlog at whatever revision
326 # value of this same revlog at whatever revision
321 # is pointed to by linknode. "Know" is in scare
327 # is pointed to by linknode. "Know" is in scare
322 # quotes because I haven't done enough examination
328 # quotes because I haven't done enough examination
323 # of edge cases to convince myself this is really
329 # of edge cases to convince myself this is really
324 # a fact - it works for all the (admittedly
330 # a fact - it works for all the (admittedly
325 # thorough) cases in our testsuite, but I would be
331 # thorough) cases in our testsuite, but I would be
326 # somewhat unsurprised to find a case in the wild
332 # somewhat unsurprised to find a case in the wild
327 # where this breaks down a bit. That said, I don't
333 # where this breaks down a bit. That said, I don't
328 # know if it would hurt anything.
334 # know if it would hurt anything.
329 for i in xrange(rev, 0, -1):
335 for i in xrange(rev, 0, -1):
330 if revlog.linkrev(i) == clrev:
336 if revlog.linkrev(i) == clrev:
331 return i
337 return i
332 # We failed to resolve a parent for this node, so
338 # We failed to resolve a parent for this node, so
333 # we crash the changegroup construction.
339 # we crash the changegroup construction.
334 raise error.Abort(
340 raise error.Abort(
335 'unable to resolve parent while packing %r %r'
341 'unable to resolve parent while packing %r %r'
336 ' for changeset %r' % (revlog.indexfile, rev, clrev))
342 ' for changeset %r' % (revlog.indexfile, rev, clrev))
337 return node.nullrev
343 return node.nullrev
338
344
339 if not linkparents or (
345 if not linkparents or (
340 revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
346 revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
341 p1, p2 = node.nullrev, node.nullrev
347 p1, p2 = node.nullrev, node.nullrev
342 elif len(linkparents) == 1:
348 elif len(linkparents) == 1:
343 p1, = sorted(local(p) for p in linkparents)
349 p1, = sorted(local(p) for p in linkparents)
344 p2 = node.nullrev
350 p2 = node.nullrev
345 else:
351 else:
346 p1, p2 = sorted(local(p) for p in linkparents)
352 p1, p2 = sorted(local(p) for p in linkparents)
347 yield ellipsisdata(
353 yield ellipsisdata(
348 self, rev, revlog, p1, p2, revlog.revision(rev), linknode)
354 self, rev, revlog, p1, p2, revlog.revision(rev), linknode)
349 extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
355 extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
350
356
351 def deltaparent(orig, self, revlog, rev, p1, p2, prev):
357 def deltaparent(orig, self, revlog, rev, p1, p2, prev):
352 if util.safehasattr(self, 'full_nodes'):
358 if util.safehasattr(self, 'full_nodes'):
353 # TODO: send better deltas when in narrow mode.
359 # TODO: send better deltas when in narrow mode.
354 #
360 #
355 # changegroup.group() loops over revisions to send,
361 # changegroup.group() loops over revisions to send,
356 # including revisions we'll skip. What this means is that
362 # including revisions we'll skip. What this means is that
357 # `prev` will be a potentially useless delta base for all
363 # `prev` will be a potentially useless delta base for all
358 # ellipsis nodes, as the client likely won't have it. In
364 # ellipsis nodes, as the client likely won't have it. In
359 # the future we should do bookkeeping about which nodes
365 # the future we should do bookkeeping about which nodes
360 # have been sent to the client, and try to be
366 # have been sent to the client, and try to be
361 # significantly smarter about delta bases. This is
367 # significantly smarter about delta bases. This is
362 # slightly tricky because this same code has to work for
368 # slightly tricky because this same code has to work for
363 # all revlogs, and we don't have the linkrev/linknode here.
369 # all revlogs, and we don't have the linkrev/linknode here.
364 return p1
370 return p1
365 return orig(self, revlog, rev, p1, p2, prev)
371 return orig(self, revlog, rev, p1, p2, prev)
366 extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
372 extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
@@ -1,210 +1,209
1
1
2 $ . "$TESTDIR/narrow-library.sh"
2 $ . "$TESTDIR/narrow-library.sh"
3
3
4 create full repo
4 create full repo
5
5
6 $ hg init master
6 $ hg init master
7 $ cd master
7 $ cd master
8 $ cat >> .hg/hgrc <<EOF
8 $ cat >> .hg/hgrc <<EOF
9 > [narrow]
9 > [narrow]
10 > serveellipses=True
10 > serveellipses=True
11 > EOF
11 > EOF
12
12
13 $ mkdir inside
13 $ mkdir inside
14 $ echo 1 > inside/f
14 $ echo 1 > inside/f
15 $ mkdir inside2
15 $ mkdir inside2
16 $ echo 1 > inside2/f
16 $ echo 1 > inside2/f
17 $ mkdir outside
17 $ mkdir outside
18 $ echo 1 > outside/f
18 $ echo 1 > outside/f
19 $ hg ci -Aqm 'initial'
19 $ hg ci -Aqm 'initial'
20
20
21 $ echo 2 > inside/f
21 $ echo 2 > inside/f
22 $ hg ci -qm 'inside 2'
22 $ hg ci -qm 'inside 2'
23
23
24 $ echo 2 > inside2/f
24 $ echo 2 > inside2/f
25 $ hg ci -qm 'inside2 2'
25 $ hg ci -qm 'inside2 2'
26
26
27 $ echo 2 > outside/f
27 $ echo 2 > outside/f
28 $ hg ci -qm 'outside 2'
28 $ hg ci -qm 'outside 2'
29
29
30 $ cd ..
30 $ cd ..
31
31
32 $ hg clone --narrow ssh://user@dummy/master narrow --include inside
32 $ hg clone --narrow ssh://user@dummy/master narrow --include inside
33 requesting all changes
33 requesting all changes
34 adding changesets
34 adding changesets
35 adding manifests
35 adding manifests
36 adding file changes
36 adding file changes
37 added 3 changesets with 2 changes to 1 files
37 added 3 changesets with 2 changes to 1 files
38 new changesets *:* (glob)
38 new changesets *:* (glob)
39 updating to branch default
39 updating to branch default
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41
41
42 $ hg clone --narrow ssh://user@dummy/master narrow2 --include inside --include inside2
42 $ hg clone --narrow ssh://user@dummy/master narrow2 --include inside --include inside2
43 requesting all changes
43 requesting all changes
44 adding changesets
44 adding changesets
45 adding manifests
45 adding manifests
46 adding file changes
46 adding file changes
47 added 4 changesets with 4 changes to 2 files
47 added 4 changesets with 4 changes to 2 files
48 new changesets *:* (glob)
48 new changesets *:* (glob)
49 updating to branch default
49 updating to branch default
50 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
51
51
52 Can push to wider repo if change does not affect paths in wider repo that are
52 Can push to wider repo if change does not affect paths in wider repo that are
53 not also in narrower repo
53 not also in narrower repo
54
54
55 $ cd narrow
55 $ cd narrow
56 $ echo 3 > inside/f
56 $ echo 3 > inside/f
57 $ hg ci -m 'inside 3'
57 $ hg ci -m 'inside 3'
58 $ hg push ssh://user@dummy/narrow2
58 $ hg push ssh://user@dummy/narrow2
59 pushing to ssh://user@dummy/narrow2
59 pushing to ssh://user@dummy/narrow2
60 searching for changes
60 searching for changes
61 remote: adding changesets
61 remote: adding changesets
62 remote: adding manifests
62 remote: adding manifests
63 remote: adding file changes
63 remote: adding file changes
64 remote: added 1 changesets with 1 changes to 1 files
64 remote: added 1 changesets with 1 changes to 1 files
65
65
66 Can push to narrower repo if change affects only paths within remote's
66 Can push to narrower repo if change affects only paths within remote's
67 narrow spec
67 narrow spec
68
68
69 $ cd ../narrow2
69 $ cd ../narrow2
70 $ cat >> .hg/hgrc <<EOF
70 $ cat >> .hg/hgrc <<EOF
71 > [narrow]
71 > [narrow]
72 > serveellipses=True
72 > serveellipses=True
73 > EOF
73 > EOF
74 $ hg co -r 'desc("inside 3")'
74 $ hg co -r 'desc("inside 3")'
75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 $ echo 4 > inside/f
76 $ echo 4 > inside/f
77 $ hg ci -m 'inside 4'
77 $ hg ci -m 'inside 4'
78 $ hg push ssh://user@dummy/narrow
78 $ hg push ssh://user@dummy/narrow
79 pushing to ssh://user@dummy/narrow
79 pushing to ssh://user@dummy/narrow
80 searching for changes
80 searching for changes
81 remote: adding changesets
81 remote: adding changesets
82 remote: adding manifests
82 remote: adding manifests
83 remote: adding file changes
83 remote: adding file changes
84 remote: added 1 changesets with 1 changes to 1 files
84 remote: added 1 changesets with 1 changes to 1 files
85
85
86 Can push to narrow repo if change affects only paths outside remote's
86 Can push to narrow repo if change affects only paths outside remote's
87 narrow spec
87 narrow spec
88
88
89 $ echo 3 > inside2/f
89 $ echo 3 > inside2/f
90 $ hg ci -m 'inside2 3'
90 $ hg ci -m 'inside2 3'
91 TODO: this should be successful
91 TODO: this should be successful
92 $ hg push ssh://user@dummy/narrow
92 $ hg push ssh://user@dummy/narrow
93 pushing to ssh://user@dummy/narrow
93 pushing to ssh://user@dummy/narrow
94 searching for changes
94 searching for changes
95 remote: adding changesets
95 remote: adding changesets
96 remote: adding manifests
96 remote: adding manifests
97 remote: adding file changes
97 remote: adding file changes
98 remote: transaction abort!
98 remote: transaction abort!
99 remote: rollback completed
99 remote: rollback completed
100 remote: abort: data/inside2/f.i@4a1aa07735e6: unknown parent!
100 remote: abort: data/inside2/f.i@4a1aa07735e6: unknown parent!
101 abort: stream ended unexpectedly (got 0 bytes, expected 4)
101 abort: stream ended unexpectedly (got 0 bytes, expected 4)
102 [255]
102 [255]
103
103
104 Can pull from wider repo if change affects only paths outside remote's
104 Can pull from wider repo if change affects only paths outside remote's
105 narrow spec
105 narrow spec
106 $ echo 4 > inside2/f
106 $ echo 4 > inside2/f
107 $ hg ci -m 'inside2 4'
107 $ hg ci -m 'inside2 4'
108 $ hg log -G -T '{rev} {node|short} {files}\n'
108 $ hg log -G -T '{rev} {node|short} {files}\n'
109 @ 7 d78a96df731d inside2/f
109 @ 7 d78a96df731d inside2/f
110 |
110 |
111 o 6 8c26f5218962 inside2/f
111 o 6 8c26f5218962 inside2/f
112 |
112 |
113 o 5 ba3480e2f9de inside/f
113 o 5 ba3480e2f9de inside/f
114 |
114 |
115 o 4 4e5edd526618 inside/f
115 o 4 4e5edd526618 inside/f
116 |
116 |
117 o 3 81e7e07b7ab0 outside/f
117 o 3 81e7e07b7ab0 outside/f
118 |
118 |
119 o 2 f3993b8c0c2b inside2/f
119 o 2 f3993b8c0c2b inside2/f
120 |
120 |
121 o 1 8cd66ca966b4 inside/f
121 o 1 8cd66ca966b4 inside/f
122 |
122 |
123 o 0 c8057d6f53ab inside/f inside2/f outside/f
123 o 0 c8057d6f53ab inside/f inside2/f outside/f
124
124
125 $ cd ../narrow
125 $ cd ../narrow
126 $ hg log -G -T '{rev} {node|short} {files}\n'
126 $ hg log -G -T '{rev} {node|short} {files}\n'
127 o 4 ba3480e2f9de inside/f
127 o 4 ba3480e2f9de inside/f
128 |
128 |
129 @ 3 4e5edd526618 inside/f
129 @ 3 4e5edd526618 inside/f
130 |
130 |
131 o 2 81e7e07b7ab0 outside/f
131 o 2 81e7e07b7ab0 outside/f
132 |
132 |
133 o 1 8cd66ca966b4 inside/f
133 o 1 8cd66ca966b4 inside/f
134 |
134 |
135 o 0 c8057d6f53ab inside/f inside2/f outside/f
135 o 0 c8057d6f53ab inside/f inside2/f outside/f
136
136
137 $ hg pull ssh://user@dummy/narrow2
137 $ hg pull ssh://user@dummy/narrow2
138 pulling from ssh://user@dummy/narrow2
138 pulling from ssh://user@dummy/narrow2
139 searching for changes
139 searching for changes
140 remote: abort: unable to resolve parent while packing 'data/inside2/f.i' 3 for changeset 5 (?)
141 adding changesets
140 adding changesets
142 remote: abort: unexpected error: unable to resolve parent while packing 'data/inside2/f.i' 3 for changeset 5
141 adding manifests
143 transaction abort!
142 adding file changes
144 rollback completed
143 added 1 changesets with 0 changes to 0 files
145 abort: pull failed on remote
144 new changesets d78a96df731d
146 [255]
145 (run 'hg update' to get a working copy)
147
146
148 Check that the resulting history is valid in the full repo
147 Check that the resulting history is valid in the full repo
149
148
150 $ cd ../narrow2
149 $ cd ../narrow2
151 $ hg push ssh://user@dummy/master
150 $ hg push ssh://user@dummy/master
152 pushing to ssh://user@dummy/master
151 pushing to ssh://user@dummy/master
153 searching for changes
152 searching for changes
154 remote: adding changesets
153 remote: adding changesets
155 remote: adding manifests
154 remote: adding manifests
156 remote: adding file changes
155 remote: adding file changes
157 remote: added 4 changesets with 4 changes to 2 files
156 remote: added 4 changesets with 4 changes to 2 files
158 $ cd ../master
157 $ cd ../master
159 $ hg verify
158 $ hg verify
160 checking changesets
159 checking changesets
161 checking manifests
160 checking manifests
162 crosschecking files in changesets and manifests
161 crosschecking files in changesets and manifests
163 checking files
162 checking files
164 3 files, 8 changesets, 10 total revisions
163 3 files, 8 changesets, 10 total revisions
165
164
166 Can not push to wider repo if change affects paths in wider repo that are
165 Can not push to wider repo if change affects paths in wider repo that are
167 not also in narrower repo
166 not also in narrower repo
168 $ cd ../master
167 $ cd ../master
169 $ hg co -r 'desc("inside2 4")'
168 $ hg co -r 'desc("inside2 4")'
170 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 $ echo 5 > inside2/f
170 $ echo 5 > inside2/f
172 $ hg ci -m 'inside2 5'
171 $ hg ci -m 'inside2 5'
173 $ hg log -G -T '{rev} {node|short} {files}\n'
172 $ hg log -G -T '{rev} {node|short} {files}\n'
174 @ 8 5970befb64ba inside2/f
173 @ 8 5970befb64ba inside2/f
175 |
174 |
176 o 7 d78a96df731d inside2/f
175 o 7 d78a96df731d inside2/f
177 |
176 |
178 o 6 8c26f5218962 inside2/f
177 o 6 8c26f5218962 inside2/f
179 |
178 |
180 o 5 ba3480e2f9de inside/f
179 o 5 ba3480e2f9de inside/f
181 |
180 |
182 o 4 4e5edd526618 inside/f
181 o 4 4e5edd526618 inside/f
183 |
182 |
184 o 3 81e7e07b7ab0 outside/f
183 o 3 81e7e07b7ab0 outside/f
185 |
184 |
186 o 2 f3993b8c0c2b inside2/f
185 o 2 f3993b8c0c2b inside2/f
187 |
186 |
188 o 1 8cd66ca966b4 inside/f
187 o 1 8cd66ca966b4 inside/f
189 |
188 |
190 o 0 c8057d6f53ab inside/f inside2/f outside/f
189 o 0 c8057d6f53ab inside/f inside2/f outside/f
191
190
192 $ cd ../narrow
191 $ cd ../narrow
193 $ hg pull
192 $ hg pull
194 pulling from ssh://user@dummy/master
193 pulling from ssh://user@dummy/master
195 searching for changes
194 searching for changes
196 adding changesets
195 adding changesets
197 adding manifests
196 adding manifests
198 adding file changes
197 adding file changes
199 added 1 changesets with 0 changes to 0 files
198 added 1 changesets with 0 changes to 0 files
200 new changesets * (glob)
199 new changesets * (glob)
201 (run 'hg update' to get a working copy)
200 (run 'hg update' to get a working copy)
202 TODO: this should tell the user that their narrow clone does not have the
201 TODO: this should tell the user that their narrow clone does not have the
203 necessary content to be able to push to the target
202 necessary content to be able to push to the target
204 $ hg push ssh://user@dummy/narrow2
203 $ hg push ssh://user@dummy/narrow2
205 pushing to ssh://user@dummy/narrow2
204 pushing to ssh://user@dummy/narrow2
206 searching for changes
205 searching for changes
207 remote has heads on branch 'default' that are not known locally: d78a96df731d
206 remote: adding changesets
208 abort: push creates new remote head 5970befb64ba!
207 remote: adding manifests
209 (pull and merge or see 'hg help push' for details about pushing new heads)
208 remote: adding file changes
210 [255]
209 remote: added 1 changesets with 0 changes to 0 files
General Comments 0
You need to be logged in to leave comments. Login now