##// END OF EJS Templates
narrow: move from ELLIPSIS_NODE_FLAG to revlog.REVIDX_ELLIPSIS...
Augie Fackler -
r36107:9445a314 default
parent child Browse files
Show More
@@ -1,385 +1,385
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 mdiff,
16 mdiff,
17 node,
17 node,
18 revlog,
18 util,
19 util,
19 )
20 )
20
21
21 from . import (
22 from . import (
22 narrowrepo,
23 narrowrepo,
23 narrowrevlog,
24 )
24 )
25
25
26 def setup():
26 def setup():
27
27
28 def supportedoutgoingversions(orig, repo):
28 def supportedoutgoingversions(orig, repo):
29 versions = orig(repo)
29 versions = orig(repo)
30 if narrowrepo.REQUIREMENT in repo.requirements:
30 if narrowrepo.REQUIREMENT in repo.requirements:
31 versions.discard('01')
31 versions.discard('01')
32 versions.discard('02')
32 versions.discard('02')
33 return versions
33 return versions
34
34
35 extensions.wrapfunction(changegroup, 'supportedoutgoingversions',
35 extensions.wrapfunction(changegroup, 'supportedoutgoingversions',
36 supportedoutgoingversions)
36 supportedoutgoingversions)
37
37
38 def prune(orig, self, revlog, missing, commonrevs):
38 def prune(orig, self, revlog, missing, commonrevs):
39 if isinstance(revlog, manifest.manifestrevlog):
39 if isinstance(revlog, manifest.manifestrevlog):
40 matcher = getattr(self._repo, 'narrowmatch',
40 matcher = getattr(self._repo, 'narrowmatch',
41 getattr(self, '_narrow_matcher', None))
41 getattr(self, '_narrow_matcher', None))
42 if (matcher is not None and
42 if (matcher is not None and
43 not matcher().visitdir(revlog._dir[:-1] or '.')):
43 not matcher().visitdir(revlog._dir[:-1] or '.')):
44 return []
44 return []
45 return orig(self, revlog, missing, commonrevs)
45 return orig(self, revlog, missing, commonrevs)
46
46
47 extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
47 extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
48
48
49 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
49 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
50 source):
50 source):
51 matcher = getattr(self._repo, 'narrowmatch',
51 matcher = getattr(self._repo, 'narrowmatch',
52 getattr(self, '_narrow_matcher', None))
52 getattr(self, '_narrow_matcher', None))
53 if matcher is not None:
53 if matcher is not None:
54 narrowmatch = matcher()
54 narrowmatch = matcher()
55 changedfiles = filter(narrowmatch, changedfiles)
55 changedfiles = filter(narrowmatch, changedfiles)
56 if getattr(self, 'is_shallow', False):
56 if getattr(self, 'is_shallow', False):
57 # See comment in generate() for why this sadness is a thing.
57 # See comment in generate() for why this sadness is a thing.
58 mfdicts = self._mfdicts
58 mfdicts = self._mfdicts
59 del self._mfdicts
59 del self._mfdicts
60 # In a shallow clone, the linknodes callback needs to also include
60 # In a shallow clone, the linknodes callback needs to also include
61 # those file nodes that are in the manifests we sent but weren't
61 # those file nodes that are in the manifests we sent but weren't
62 # introduced by those manifests.
62 # introduced by those manifests.
63 commonctxs = [self._repo[c] for c in commonrevs]
63 commonctxs = [self._repo[c] for c in commonrevs]
64 oldlinknodes = linknodes
64 oldlinknodes = linknodes
65 clrev = self._repo.changelog.rev
65 clrev = self._repo.changelog.rev
66 def linknodes(flog, fname):
66 def linknodes(flog, fname):
67 for c in commonctxs:
67 for c in commonctxs:
68 try:
68 try:
69 fnode = c.filenode(fname)
69 fnode = c.filenode(fname)
70 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
70 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
71 except error.ManifestLookupError:
71 except error.ManifestLookupError:
72 pass
72 pass
73 links = oldlinknodes(flog, fname)
73 links = oldlinknodes(flog, fname)
74 if len(links) != len(mfdicts):
74 if len(links) != len(mfdicts):
75 for mf, lr in mfdicts:
75 for mf, lr in mfdicts:
76 fnode = mf.get(fname, None)
76 fnode = mf.get(fname, None)
77 if fnode in links:
77 if fnode in links:
78 links[fnode] = min(links[fnode], lr, key=clrev)
78 links[fnode] = min(links[fnode], lr, key=clrev)
79 elif fnode:
79 elif fnode:
80 links[fnode] = lr
80 links[fnode] = lr
81 return links
81 return links
82 return orig(self, changedfiles, linknodes, commonrevs, source)
82 return orig(self, changedfiles, linknodes, commonrevs, source)
83 extensions.wrapfunction(
83 extensions.wrapfunction(
84 changegroup.cg1packer, 'generatefiles', generatefiles)
84 changegroup.cg1packer, 'generatefiles', generatefiles)
85
85
86 def ellipsisdata(packer, rev, revlog, p1, p2, data, linknode):
86 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
87 n = revlog.node(rev)
87 n = revlog_.node(rev)
88 p1n, p2n = revlog.node(p1), revlog.node(p2)
88 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
89 flags = revlog.flags(rev)
89 flags = revlog_.flags(rev)
90 flags |= narrowrevlog.ELLIPSIS_NODE_FLAG
90 flags |= revlog.REVIDX_ELLIPSIS
91 meta = packer.builddeltaheader(
91 meta = packer.builddeltaheader(
92 n, p1n, p2n, node.nullid, linknode, flags)
92 n, p1n, p2n, node.nullid, linknode, flags)
93 # TODO: try and actually send deltas for ellipsis data blocks
93 # TODO: try and actually send deltas for ellipsis data blocks
94 diffheader = mdiff.trivialdiffheader(len(data))
94 diffheader = mdiff.trivialdiffheader(len(data))
95 l = len(meta) + len(diffheader) + len(data)
95 l = len(meta) + len(diffheader) + len(data)
96 return ''.join((changegroup.chunkheader(l),
96 return ''.join((changegroup.chunkheader(l),
97 meta,
97 meta,
98 diffheader,
98 diffheader,
99 data))
99 data))
100
100
101 def close(orig, self):
101 def close(orig, self):
102 getattr(self, 'clrev_to_localrev', {}).clear()
102 getattr(self, 'clrev_to_localrev', {}).clear()
103 if getattr(self, 'next_clrev_to_localrev', {}):
103 if getattr(self, 'next_clrev_to_localrev', {}):
104 self.clrev_to_localrev = self.next_clrev_to_localrev
104 self.clrev_to_localrev = self.next_clrev_to_localrev
105 del self.next_clrev_to_localrev
105 del self.next_clrev_to_localrev
106 self.changelog_done = True
106 self.changelog_done = True
107 return orig(self)
107 return orig(self)
108 extensions.wrapfunction(changegroup.cg1packer, 'close', close)
108 extensions.wrapfunction(changegroup.cg1packer, 'close', close)
109
109
110 # In a perfect world, we'd generate better ellipsis-ified graphs
110 # In a perfect world, we'd generate better ellipsis-ified graphs
111 # for non-changelog revlogs. In practice, we haven't started doing
111 # for non-changelog revlogs. In practice, we haven't started doing
112 # that yet, so the resulting DAGs for the manifestlog and filelogs
112 # that yet, so the resulting DAGs for the manifestlog and filelogs
113 # are actually full of bogus parentage on all the ellipsis
113 # are actually full of bogus parentage on all the ellipsis
114 # nodes. This has the side effect that, while the contents are
114 # nodes. This has the side effect that, while the contents are
115 # correct, the individual DAGs might be completely out of whack in
115 # correct, the individual DAGs might be completely out of whack in
116 # a case like 882681bc3166 and its ancestors (back about 10
116 # a case like 882681bc3166 and its ancestors (back about 10
117 # revisions or so) in the main hg repo.
117 # revisions or so) in the main hg repo.
118 #
118 #
119 # The one invariant we *know* holds is that the new (potentially
119 # The one invariant we *know* holds is that the new (potentially
120 # bogus) DAG shape will be valid if we order the nodes in the
120 # bogus) DAG shape will be valid if we order the nodes in the
121 # order that they're introduced in dramatis personae by the
121 # order that they're introduced in dramatis personae by the
122 # changelog, so what we do is we sort the non-changelog histories
122 # changelog, so what we do is we sort the non-changelog histories
123 # by the order in which they are used by the changelog.
123 # by the order in which they are used by the changelog.
124 def _sortgroup(orig, self, revlog, nodelist, lookup):
124 def _sortgroup(orig, self, revlog, nodelist, lookup):
125 if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
125 if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
126 return orig(self, revlog, nodelist, lookup)
126 return orig(self, revlog, nodelist, lookup)
127 key = lambda n: self.clnode_to_rev[lookup(n)]
127 key = lambda n: self.clnode_to_rev[lookup(n)]
128 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
128 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
129
129
130 extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
130 extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
131
131
132 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
132 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
133 '''yield a sequence of changegroup chunks (strings)'''
133 '''yield a sequence of changegroup chunks (strings)'''
134 # Note: other than delegating to orig, the only deviation in
134 # Note: other than delegating to orig, the only deviation in
135 # logic from normal hg's generate is marked with BEGIN/END
135 # logic from normal hg's generate is marked with BEGIN/END
136 # NARROW HACK.
136 # NARROW HACK.
137 if not util.safehasattr(self, 'full_nodes'):
137 if not util.safehasattr(self, 'full_nodes'):
138 # not sending a narrow bundle
138 # not sending a narrow bundle
139 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
139 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
140 yield x
140 yield x
141 return
141 return
142
142
143 repo = self._repo
143 repo = self._repo
144 cl = repo.changelog
144 cl = repo.changelog
145 mfl = repo.manifestlog
145 mfl = repo.manifestlog
146 mfrevlog = mfl._revlog
146 mfrevlog = mfl._revlog
147
147
148 clrevorder = {}
148 clrevorder = {}
149 mfs = {} # needed manifests
149 mfs = {} # needed manifests
150 fnodes = {} # needed file nodes
150 fnodes = {} # needed file nodes
151 changedfiles = set()
151 changedfiles = set()
152
152
153 # Callback for the changelog, used to collect changed files and manifest
153 # Callback for the changelog, used to collect changed files and manifest
154 # nodes.
154 # nodes.
155 # Returns the linkrev node (identity in the changelog case).
155 # Returns the linkrev node (identity in the changelog case).
156 def lookupcl(x):
156 def lookupcl(x):
157 c = cl.read(x)
157 c = cl.read(x)
158 clrevorder[x] = len(clrevorder)
158 clrevorder[x] = len(clrevorder)
159 # BEGIN NARROW HACK
159 # BEGIN NARROW HACK
160 #
160 #
161 # Only update mfs if x is going to be sent. Otherwise we
161 # Only update mfs if x is going to be sent. Otherwise we
162 # end up with bogus linkrevs specified for manifests and
162 # end up with bogus linkrevs specified for manifests and
163 # we skip some manifest nodes that we should otherwise
163 # we skip some manifest nodes that we should otherwise
164 # have sent.
164 # have sent.
165 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
165 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
166 n = c[0]
166 n = c[0]
167 # record the first changeset introducing this manifest version
167 # record the first changeset introducing this manifest version
168 mfs.setdefault(n, x)
168 mfs.setdefault(n, x)
169 # Set this narrow-specific dict so we have the lowest manifest
169 # Set this narrow-specific dict so we have the lowest manifest
170 # revnum to look up for this cl revnum. (Part of mapping
170 # revnum to look up for this cl revnum. (Part of mapping
171 # changelog ellipsis parents to manifest ellipsis parents)
171 # changelog ellipsis parents to manifest ellipsis parents)
172 self.next_clrev_to_localrev.setdefault(cl.rev(x),
172 self.next_clrev_to_localrev.setdefault(cl.rev(x),
173 mfrevlog.rev(n))
173 mfrevlog.rev(n))
174 # We can't trust the changed files list in the changeset if the
174 # We can't trust the changed files list in the changeset if the
175 # client requested a shallow clone.
175 # client requested a shallow clone.
176 if self.is_shallow:
176 if self.is_shallow:
177 changedfiles.update(mfl[c[0]].read().keys())
177 changedfiles.update(mfl[c[0]].read().keys())
178 else:
178 else:
179 changedfiles.update(c[3])
179 changedfiles.update(c[3])
180 # END NARROW HACK
180 # END NARROW HACK
181 # Record a complete list of potentially-changed files in
181 # Record a complete list of potentially-changed files in
182 # this manifest.
182 # this manifest.
183 return x
183 return x
184
184
185 self._verbosenote(_('uncompressed size of bundle content:\n'))
185 self._verbosenote(_('uncompressed size of bundle content:\n'))
186 size = 0
186 size = 0
187 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
187 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
188 size += len(chunk)
188 size += len(chunk)
189 yield chunk
189 yield chunk
190 self._verbosenote(_('%8.i (changelog)\n') % size)
190 self._verbosenote(_('%8.i (changelog)\n') % size)
191
191
192 # We need to make sure that the linkrev in the changegroup refers to
192 # We need to make sure that the linkrev in the changegroup refers to
193 # the first changeset that introduced the manifest or file revision.
193 # the first changeset that introduced the manifest or file revision.
194 # The fastpath is usually safer than the slowpath, because the filelogs
194 # The fastpath is usually safer than the slowpath, because the filelogs
195 # are walked in revlog order.
195 # are walked in revlog order.
196 #
196 #
197 # When taking the slowpath with reorder=None and the manifest revlog
197 # When taking the slowpath with reorder=None and the manifest revlog
198 # uses generaldelta, the manifest may be walked in the "wrong" order.
198 # uses generaldelta, the manifest may be walked in the "wrong" order.
199 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
199 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
200 # cc0ff93d0c0c).
200 # cc0ff93d0c0c).
201 #
201 #
202 # When taking the fastpath, we are only vulnerable to reordering
202 # When taking the fastpath, we are only vulnerable to reordering
203 # of the changelog itself. The changelog never uses generaldelta, so
203 # of the changelog itself. The changelog never uses generaldelta, so
204 # it is only reordered when reorder=True. To handle this case, we
204 # it is only reordered when reorder=True. To handle this case, we
205 # simply take the slowpath, which already has the 'clrevorder' logic.
205 # simply take the slowpath, which already has the 'clrevorder' logic.
206 # This was also fixed in cc0ff93d0c0c.
206 # This was also fixed in cc0ff93d0c0c.
207 fastpathlinkrev = fastpathlinkrev and not self._reorder
207 fastpathlinkrev = fastpathlinkrev and not self._reorder
208 # Treemanifests don't work correctly with fastpathlinkrev
208 # Treemanifests don't work correctly with fastpathlinkrev
209 # either, because we don't discover which directory nodes to
209 # either, because we don't discover which directory nodes to
210 # send along with files. This could probably be fixed.
210 # send along with files. This could probably be fixed.
211 fastpathlinkrev = fastpathlinkrev and (
211 fastpathlinkrev = fastpathlinkrev and (
212 'treemanifest' not in repo.requirements)
212 'treemanifest' not in repo.requirements)
213 # Shallow clones also don't work correctly with fastpathlinkrev
213 # Shallow clones also don't work correctly with fastpathlinkrev
214 # because file nodes may need to be sent for a manifest even if they
214 # because file nodes may need to be sent for a manifest even if they
215 # weren't introduced by that manifest.
215 # weren't introduced by that manifest.
216 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
216 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
217
217
218 moreargs = []
218 moreargs = []
219 if self.generatemanifests.func_code.co_argcount == 7:
219 if self.generatemanifests.func_code.co_argcount == 7:
220 # The source argument was added to generatemanifests in hg in
220 # The source argument was added to generatemanifests in hg in
221 # 75cc1f1e11f2 (2017/09/11).
221 # 75cc1f1e11f2 (2017/09/11).
222 moreargs.append(source)
222 moreargs.append(source)
223 for chunk in self.generatemanifests(commonrevs, clrevorder,
223 for chunk in self.generatemanifests(commonrevs, clrevorder,
224 fastpathlinkrev, mfs, fnodes, *moreargs):
224 fastpathlinkrev, mfs, fnodes, *moreargs):
225 yield chunk
225 yield chunk
226 # BEGIN NARROW HACK
226 # BEGIN NARROW HACK
227 mfdicts = None
227 mfdicts = None
228 if self.is_shallow:
228 if self.is_shallow:
229 mfdicts = [(self._repo.manifestlog[n].read(), lr)
229 mfdicts = [(self._repo.manifestlog[n].read(), lr)
230 for (n, lr) in mfs.iteritems()]
230 for (n, lr) in mfs.iteritems()]
231 # END NARROW HACK
231 # END NARROW HACK
232 mfs.clear()
232 mfs.clear()
233 clrevs = set(cl.rev(x) for x in clnodes)
233 clrevs = set(cl.rev(x) for x in clnodes)
234
234
235 if not fastpathlinkrev:
235 if not fastpathlinkrev:
236 def linknodes(unused, fname):
236 def linknodes(unused, fname):
237 return fnodes.get(fname, {})
237 return fnodes.get(fname, {})
238 else:
238 else:
239 cln = cl.node
239 cln = cl.node
240 def linknodes(filerevlog, fname):
240 def linknodes(filerevlog, fname):
241 llr = filerevlog.linkrev
241 llr = filerevlog.linkrev
242 fln = filerevlog.node
242 fln = filerevlog.node
243 revs = ((r, llr(r)) for r in filerevlog)
243 revs = ((r, llr(r)) for r in filerevlog)
244 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
244 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
245
245
246 # BEGIN NARROW HACK
246 # BEGIN NARROW HACK
247 #
247 #
248 # We need to pass the mfdicts variable down into
248 # We need to pass the mfdicts variable down into
249 # generatefiles(), but more than one command might have
249 # generatefiles(), but more than one command might have
250 # wrapped generatefiles so we can't modify the function
250 # wrapped generatefiles so we can't modify the function
251 # signature. Instead, we pass the data to ourselves using an
251 # signature. Instead, we pass the data to ourselves using an
252 # instance attribute. I'm sorry.
252 # instance attribute. I'm sorry.
253 self._mfdicts = mfdicts
253 self._mfdicts = mfdicts
254 # END NARROW HACK
254 # END NARROW HACK
255 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
255 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
256 source):
256 source):
257 yield chunk
257 yield chunk
258
258
259 yield self.close()
259 yield self.close()
260
260
261 if clnodes:
261 if clnodes:
262 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
262 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
263 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
263 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
264
264
265 def revchunk(orig, self, revlog, rev, prev, linknode):
265 def revchunk(orig, self, revlog, rev, prev, linknode):
266 if not util.safehasattr(self, 'full_nodes'):
266 if not util.safehasattr(self, 'full_nodes'):
267 # not sending a narrow changegroup
267 # not sending a narrow changegroup
268 for x in orig(self, revlog, rev, prev, linknode):
268 for x in orig(self, revlog, rev, prev, linknode):
269 yield x
269 yield x
270 return
270 return
271 # build up some mapping information that's useful later. See
271 # build up some mapping information that's useful later. See
272 # the local() nested function below.
272 # the local() nested function below.
273 if not self.changelog_done:
273 if not self.changelog_done:
274 self.clnode_to_rev[linknode] = rev
274 self.clnode_to_rev[linknode] = rev
275 linkrev = rev
275 linkrev = rev
276 self.clrev_to_localrev[linkrev] = rev
276 self.clrev_to_localrev[linkrev] = rev
277 else:
277 else:
278 linkrev = self.clnode_to_rev[linknode]
278 linkrev = self.clnode_to_rev[linknode]
279 self.clrev_to_localrev[linkrev] = rev
279 self.clrev_to_localrev[linkrev] = rev
280 # This is a node to send in full, because the changeset it
280 # This is a node to send in full, because the changeset it
281 # corresponds to was a full changeset.
281 # corresponds to was a full changeset.
282 if linknode in self.full_nodes:
282 if linknode in self.full_nodes:
283 for x in orig(self, revlog, rev, prev, linknode):
283 for x in orig(self, revlog, rev, prev, linknode):
284 yield x
284 yield x
285 return
285 return
286 # At this point, a node can either be one we should skip or an
286 # At this point, a node can either be one we should skip or an
287 # ellipsis. If it's not an ellipsis, bail immediately.
287 # ellipsis. If it's not an ellipsis, bail immediately.
288 if linkrev not in self.precomputed_ellipsis:
288 if linkrev not in self.precomputed_ellipsis:
289 return
289 return
290 linkparents = self.precomputed_ellipsis[linkrev]
290 linkparents = self.precomputed_ellipsis[linkrev]
291 def local(clrev):
291 def local(clrev):
292 """Turn a changelog revnum into a local revnum.
292 """Turn a changelog revnum into a local revnum.
293
293
294 The ellipsis dag is stored as revnums on the changelog,
294 The ellipsis dag is stored as revnums on the changelog,
295 but when we're producing ellipsis entries for
295 but when we're producing ellipsis entries for
296 non-changelog revlogs, we need to turn those numbers into
296 non-changelog revlogs, we need to turn those numbers into
297 something local. This does that for us, and during the
297 something local. This does that for us, and during the
298 changelog sending phase will also expand the stored
298 changelog sending phase will also expand the stored
299 mappings as needed.
299 mappings as needed.
300 """
300 """
301 if clrev == node.nullrev:
301 if clrev == node.nullrev:
302 return node.nullrev
302 return node.nullrev
303 if not self.changelog_done:
303 if not self.changelog_done:
304 # If we're doing the changelog, it's possible that we
304 # If we're doing the changelog, it's possible that we
305 # have a parent that is already on the client, and we
305 # have a parent that is already on the client, and we
306 # need to store some extra mapping information so that
306 # need to store some extra mapping information so that
307 # our contained ellipsis nodes will be able to resolve
307 # our contained ellipsis nodes will be able to resolve
308 # their parents.
308 # their parents.
309 if clrev not in self.clrev_to_localrev:
309 if clrev not in self.clrev_to_localrev:
310 clnode = revlog.node(clrev)
310 clnode = revlog.node(clrev)
311 self.clnode_to_rev[clnode] = clrev
311 self.clnode_to_rev[clnode] = clrev
312 return clrev
312 return clrev
313 # Walk the ellipsis-ized changelog breadth-first looking for a
313 # Walk the ellipsis-ized changelog breadth-first looking for a
314 # change that has been linked from the current revlog.
314 # change that has been linked from the current revlog.
315 #
315 #
316 # For a flat manifest revlog only a single step should be necessary
316 # For a flat manifest revlog only a single step should be necessary
317 # as all relevant changelog entries are relevant to the flat
317 # as all relevant changelog entries are relevant to the flat
318 # manifest.
318 # manifest.
319 #
319 #
320 # For a filelog or tree manifest dirlog however not every changelog
320 # For a filelog or tree manifest dirlog however not every changelog
321 # entry will have been relevant, so we need to skip some changelog
321 # entry will have been relevant, so we need to skip some changelog
322 # nodes even after ellipsis-izing.
322 # nodes even after ellipsis-izing.
323 walk = [clrev]
323 walk = [clrev]
324 while walk:
324 while walk:
325 p = walk[0]
325 p = walk[0]
326 walk = walk[1:]
326 walk = walk[1:]
327 if p in self.clrev_to_localrev:
327 if p in self.clrev_to_localrev:
328 return self.clrev_to_localrev[p]
328 return self.clrev_to_localrev[p]
329 elif p in self.full_nodes:
329 elif p in self.full_nodes:
330 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
330 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
331 if pp != node.nullrev])
331 if pp != node.nullrev])
332 elif p in self.precomputed_ellipsis:
332 elif p in self.precomputed_ellipsis:
333 walk.extend([pp for pp in self.precomputed_ellipsis[p]
333 walk.extend([pp for pp in self.precomputed_ellipsis[p]
334 if pp != node.nullrev])
334 if pp != node.nullrev])
335 else:
335 else:
336 # In this case, we've got an ellipsis with parents
336 # In this case, we've got an ellipsis with parents
337 # outside the current bundle (likely an
337 # outside the current bundle (likely an
338 # incremental pull). We "know" that we can use the
338 # incremental pull). We "know" that we can use the
339 # value of this same revlog at whatever revision
339 # value of this same revlog at whatever revision
340 # is pointed to by linknode. "Know" is in scare
340 # is pointed to by linknode. "Know" is in scare
341 # quotes because I haven't done enough examination
341 # quotes because I haven't done enough examination
342 # of edge cases to convince myself this is really
342 # of edge cases to convince myself this is really
343 # a fact - it works for all the (admittedly
343 # a fact - it works for all the (admittedly
344 # thorough) cases in our testsuite, but I would be
344 # thorough) cases in our testsuite, but I would be
345 # somewhat unsurprised to find a case in the wild
345 # somewhat unsurprised to find a case in the wild
346 # where this breaks down a bit. That said, I don't
346 # where this breaks down a bit. That said, I don't
347 # know if it would hurt anything.
347 # know if it would hurt anything.
348 for i in xrange(rev, 0, -1):
348 for i in xrange(rev, 0, -1):
349 if revlog.linkrev(i) == clrev:
349 if revlog.linkrev(i) == clrev:
350 return i
350 return i
351 # We failed to resolve a parent for this node, so
351 # We failed to resolve a parent for this node, so
352 # we crash the changegroup construction.
352 # we crash the changegroup construction.
353 raise error.Abort(
353 raise error.Abort(
354 'unable to resolve parent while packing %r %r'
354 'unable to resolve parent while packing %r %r'
355 ' for changeset %r' % (revlog.indexfile, rev, clrev))
355 ' for changeset %r' % (revlog.indexfile, rev, clrev))
356 return node.nullrev
356 return node.nullrev
357
357
358 if not linkparents or (
358 if not linkparents or (
359 revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
359 revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
360 p1, p2 = node.nullrev, node.nullrev
360 p1, p2 = node.nullrev, node.nullrev
361 elif len(linkparents) == 1:
361 elif len(linkparents) == 1:
362 p1, = sorted(local(p) for p in linkparents)
362 p1, = sorted(local(p) for p in linkparents)
363 p2 = node.nullrev
363 p2 = node.nullrev
364 else:
364 else:
365 p1, p2 = sorted(local(p) for p in linkparents)
365 p1, p2 = sorted(local(p) for p in linkparents)
366 yield ellipsisdata(
366 yield ellipsisdata(
367 self, rev, revlog, p1, p2, revlog.revision(rev), linknode)
367 self, rev, revlog, p1, p2, revlog.revision(rev), linknode)
368 extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
368 extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
369
369
370 def deltaparent(orig, self, revlog, rev, p1, p2, prev):
370 def deltaparent(orig, self, revlog, rev, p1, p2, prev):
371 if util.safehasattr(self, 'full_nodes'):
371 if util.safehasattr(self, 'full_nodes'):
372 # TODO: send better deltas when in narrow mode.
372 # TODO: send better deltas when in narrow mode.
373 #
373 #
374 # changegroup.group() loops over revisions to send,
374 # changegroup.group() loops over revisions to send,
375 # including revisions we'll skip. What this means is that
375 # including revisions we'll skip. What this means is that
376 # `prev` will be a potentially useless delta base for all
376 # `prev` will be a potentially useless delta base for all
377 # ellipsis nodes, as the client likely won't have it. In
377 # ellipsis nodes, as the client likely won't have it. In
378 # the future we should do bookkeeping about which nodes
378 # the future we should do bookkeeping about which nodes
379 # have been sent to the client, and try to be
379 # have been sent to the client, and try to be
380 # significantly smarter about delta bases. This is
380 # significantly smarter about delta bases. This is
381 # slightly tricky because this same code has to work for
381 # slightly tricky because this same code has to work for
382 # all revlogs, and we don't have the linkrev/linknode here.
382 # all revlogs, and we don't have the linkrev/linknode here.
383 return p1
383 return p1
384 return orig(self, revlog, rev, p1, p2, prev)
384 return orig(self, revlog, rev, p1, p2, prev)
385 extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
385 extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
@@ -1,161 +1,156
1 # narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
1 # narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
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 import (
10 from mercurial import (
11 manifest,
11 manifest,
12 revlog,
12 revlog,
13 util,
13 util,
14 )
14 )
15
15
16 ELLIPSIS_NODE_FLAG = 1 << 14
17 revlog.REVIDX_KNOWN_FLAGS |= ELLIPSIS_NODE_FLAG
18 if ELLIPSIS_NODE_FLAG not in revlog.REVIDX_FLAGS_ORDER:
19 revlog.REVIDX_FLAGS_ORDER.append(ELLIPSIS_NODE_FLAG)
20
21 def readtransform(self, text):
16 def readtransform(self, text):
22 return text, False
17 return text, False
23
18
24 def writetransform(self, text):
19 def writetransform(self, text):
25 return text, False
20 return text, False
26
21
27 def rawtransform(self, text):
22 def rawtransform(self, text):
28 return False
23 return False
29
24
30 revlog.addflagprocessor(ELLIPSIS_NODE_FLAG,
25 revlog.addflagprocessor(revlog.REVIDX_ELLIPSIS,
31 (readtransform, writetransform, rawtransform))
26 (readtransform, writetransform, rawtransform))
32
27
33 def setup():
28 def setup():
34 # We just wanted to add the flag processor, which is done at module
29 # We just wanted to add the flag processor, which is done at module
35 # load time.
30 # load time.
36 pass
31 pass
37
32
38 class excludeddir(manifest.treemanifest):
33 class excludeddir(manifest.treemanifest):
39 def __init__(self, dir, node):
34 def __init__(self, dir, node):
40 super(excludeddir, self).__init__(dir)
35 super(excludeddir, self).__init__(dir)
41 self._node = node
36 self._node = node
42 # Add an empty file, which will be included by iterators and such,
37 # Add an empty file, which will be included by iterators and such,
43 # appearing as the directory itself (i.e. something like "dir/")
38 # appearing as the directory itself (i.e. something like "dir/")
44 self._files[''] = node
39 self._files[''] = node
45 self._flags[''] = 't'
40 self._flags[''] = 't'
46
41
47 # Manifests outside the narrowspec should never be modified, so avoid
42 # Manifests outside the narrowspec should never be modified, so avoid
48 # copying. This makes a noticeable difference when there are very many
43 # copying. This makes a noticeable difference when there are very many
49 # directories outside the narrowspec. Also, it makes sense for the copy to
44 # directories outside the narrowspec. Also, it makes sense for the copy to
50 # be of the same type as the original, which would not happen with the
45 # be of the same type as the original, which would not happen with the
51 # super type's copy().
46 # super type's copy().
52 def copy(self):
47 def copy(self):
53 return self
48 return self
54
49
55 class excludeddirmanifestctx(manifest.treemanifestctx):
50 class excludeddirmanifestctx(manifest.treemanifestctx):
56 def __init__(self, dir, node):
51 def __init__(self, dir, node):
57 self._dir = dir
52 self._dir = dir
58 self._node = node
53 self._node = node
59
54
60 def read(self):
55 def read(self):
61 return excludeddir(self._dir, self._node)
56 return excludeddir(self._dir, self._node)
62
57
63 def write(self, *args):
58 def write(self, *args):
64 raise AssertionError('Attempt to write manifest from excluded dir %s' %
59 raise AssertionError('Attempt to write manifest from excluded dir %s' %
65 self._dir)
60 self._dir)
66
61
67 class excludedmanifestrevlog(manifest.manifestrevlog):
62 class excludedmanifestrevlog(manifest.manifestrevlog):
68 def __init__(self, dir):
63 def __init__(self, dir):
69 self._dir = dir
64 self._dir = dir
70
65
71 def __len__(self):
66 def __len__(self):
72 raise AssertionError('Attempt to get length of excluded dir %s' %
67 raise AssertionError('Attempt to get length of excluded dir %s' %
73 self._dir)
68 self._dir)
74
69
75 def rev(self, node):
70 def rev(self, node):
76 raise AssertionError('Attempt to get rev from excluded dir %s' %
71 raise AssertionError('Attempt to get rev from excluded dir %s' %
77 self._dir)
72 self._dir)
78
73
79 def linkrev(self, node):
74 def linkrev(self, node):
80 raise AssertionError('Attempt to get linkrev from excluded dir %s' %
75 raise AssertionError('Attempt to get linkrev from excluded dir %s' %
81 self._dir)
76 self._dir)
82
77
83 def node(self, rev):
78 def node(self, rev):
84 raise AssertionError('Attempt to get node from excluded dir %s' %
79 raise AssertionError('Attempt to get node from excluded dir %s' %
85 self._dir)
80 self._dir)
86
81
87 def add(self, *args, **kwargs):
82 def add(self, *args, **kwargs):
88 # We should never write entries in dirlogs outside the narrow clone.
83 # We should never write entries in dirlogs outside the narrow clone.
89 # However, the method still gets called from writesubtree() in
84 # However, the method still gets called from writesubtree() in
90 # _addtree(), so we need to handle it. We should possibly make that
85 # _addtree(), so we need to handle it. We should possibly make that
91 # avoid calling add() with a clean manifest (_dirty is always False
86 # avoid calling add() with a clean manifest (_dirty is always False
92 # in excludeddir instances).
87 # in excludeddir instances).
93 pass
88 pass
94
89
95 def makenarrowmanifestrevlog(mfrevlog, repo):
90 def makenarrowmanifestrevlog(mfrevlog, repo):
96 if util.safehasattr(mfrevlog, '_narrowed'):
91 if util.safehasattr(mfrevlog, '_narrowed'):
97 return
92 return
98
93
99 class narrowmanifestrevlog(mfrevlog.__class__):
94 class narrowmanifestrevlog(mfrevlog.__class__):
100 # This function is called via debug{revlog,index,data}, but also during
95 # This function is called via debug{revlog,index,data}, but also during
101 # at least some push operations. This will be used to wrap/exclude the
96 # at least some push operations. This will be used to wrap/exclude the
102 # child directories when using treemanifests.
97 # child directories when using treemanifests.
103 def dirlog(self, dir):
98 def dirlog(self, dir):
104 if dir and not dir.endswith('/'):
99 if dir and not dir.endswith('/'):
105 dir = dir + '/'
100 dir = dir + '/'
106 if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
101 if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
107 return excludedmanifestrevlog(dir)
102 return excludedmanifestrevlog(dir)
108 result = super(narrowmanifestrevlog, self).dirlog(dir)
103 result = super(narrowmanifestrevlog, self).dirlog(dir)
109 makenarrowmanifestrevlog(result, repo)
104 makenarrowmanifestrevlog(result, repo)
110 return result
105 return result
111
106
112 mfrevlog.__class__ = narrowmanifestrevlog
107 mfrevlog.__class__ = narrowmanifestrevlog
113 mfrevlog._narrowed = True
108 mfrevlog._narrowed = True
114
109
115 def makenarrowmanifestlog(mfl, repo):
110 def makenarrowmanifestlog(mfl, repo):
116 class narrowmanifestlog(mfl.__class__):
111 class narrowmanifestlog(mfl.__class__):
117 def get(self, dir, node, verify=True):
112 def get(self, dir, node, verify=True):
118 if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
113 if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
119 return excludeddirmanifestctx(dir, node)
114 return excludeddirmanifestctx(dir, node)
120 return super(narrowmanifestlog, self).get(dir, node, verify=verify)
115 return super(narrowmanifestlog, self).get(dir, node, verify=verify)
121 mfl.__class__ = narrowmanifestlog
116 mfl.__class__ = narrowmanifestlog
122
117
123 def makenarrowfilelog(fl, narrowmatch):
118 def makenarrowfilelog(fl, narrowmatch):
124 class narrowfilelog(fl.__class__):
119 class narrowfilelog(fl.__class__):
125 def renamed(self, node):
120 def renamed(self, node):
126 m = super(narrowfilelog, self).renamed(node)
121 m = super(narrowfilelog, self).renamed(node)
127 if m and not narrowmatch(m[0]):
122 if m and not narrowmatch(m[0]):
128 return None
123 return None
129 return m
124 return m
130
125
131 def size(self, rev):
126 def size(self, rev):
132 # We take advantage of the fact that remotefilelog
127 # We take advantage of the fact that remotefilelog
133 # lacks a node() method to just skip the
128 # lacks a node() method to just skip the
134 # rename-checking logic when on remotefilelog. This
129 # rename-checking logic when on remotefilelog. This
135 # might be incorrect on other non-revlog-based storage
130 # might be incorrect on other non-revlog-based storage
136 # engines, but for now this seems to be fine.
131 # engines, but for now this seems to be fine.
137 if util.safehasattr(self, 'node'):
132 if util.safehasattr(self, 'node'):
138 node = self.node(rev)
133 node = self.node(rev)
139 # Because renamed() is overridden above to
134 # Because renamed() is overridden above to
140 # sometimes return None even if there is metadata
135 # sometimes return None even if there is metadata
141 # in the revlog, size can be incorrect for
136 # in the revlog, size can be incorrect for
142 # copies/renames, so we need to make sure we call
137 # copies/renames, so we need to make sure we call
143 # the super class's implementation of renamed()
138 # the super class's implementation of renamed()
144 # for the purpose of size calculation.
139 # for the purpose of size calculation.
145 if super(narrowfilelog, self).renamed(node):
140 if super(narrowfilelog, self).renamed(node):
146 return len(self.read(node))
141 return len(self.read(node))
147 return super(narrowfilelog, self).size(rev)
142 return super(narrowfilelog, self).size(rev)
148
143
149 def cmp(self, node, text):
144 def cmp(self, node, text):
150 different = super(narrowfilelog, self).cmp(node, text)
145 different = super(narrowfilelog, self).cmp(node, text)
151 if different:
146 if different:
152 # Similar to size() above, if the file was copied from
147 # Similar to size() above, if the file was copied from
153 # a file outside the narrowspec, the super class's
148 # a file outside the narrowspec, the super class's
154 # would have returned True because we tricked it into
149 # would have returned True because we tricked it into
155 # thinking that the file was not renamed.
150 # thinking that the file was not renamed.
156 if super(narrowfilelog, self).renamed(node):
151 if super(narrowfilelog, self).renamed(node):
157 t2 = self.read(node)
152 t2 = self.read(node)
158 return t2 != text
153 return t2 != text
159 return different
154 return different
160
155
161 fl.__class__ = narrowfilelog
156 fl.__class__ = narrowfilelog
@@ -1,50 +1,49
1 # narrowtemplates.py - added template keywords for narrow clones
1 # narrowtemplates.py - added template keywords for narrow clones
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 import (
10 from mercurial import (
11 revlog,
11 revset,
12 revset,
12 templatekw,
13 templatekw,
13 util,
14 util,
14 )
15 )
15
16
16 from . import narrowrevlog
17
18 def _isellipsis(repo, rev):
17 def _isellipsis(repo, rev):
19 if repo.changelog.flags(rev) & narrowrevlog.ELLIPSIS_NODE_FLAG:
18 if repo.changelog.flags(rev) & revlog.REVIDX_ELLIPSIS:
20 return True
19 return True
21 return False
20 return False
22
21
23 def ellipsis(repo, ctx, templ, **args):
22 def ellipsis(repo, ctx, templ, **args):
24 """:ellipsis: String. 'ellipsis' if the change is an ellipsis node,
23 """:ellipsis: String. 'ellipsis' if the change is an ellipsis node,
25 else ''."""
24 else ''."""
26 if _isellipsis(repo, ctx.rev()):
25 if _isellipsis(repo, ctx.rev()):
27 return 'ellipsis'
26 return 'ellipsis'
28 return ''
27 return ''
29
28
30 def outsidenarrow(repo, ctx, templ, **args):
29 def outsidenarrow(repo, ctx, templ, **args):
31 """:outsidenarrow: String. 'outsidenarrow' if the change affects no
30 """:outsidenarrow: String. 'outsidenarrow' if the change affects no
32 tracked files, else ''."""
31 tracked files, else ''."""
33 if util.safehasattr(repo, 'narrowmatch'):
32 if util.safehasattr(repo, 'narrowmatch'):
34 m = repo.narrowmatch()
33 m = repo.narrowmatch()
35 if not any(m(f) for f in ctx.files()):
34 if not any(m(f) for f in ctx.files()):
36 return 'outsidenarrow'
35 return 'outsidenarrow'
37 return ''
36 return ''
38
37
39 def ellipsisrevset(repo, subset, x):
38 def ellipsisrevset(repo, subset, x):
40 """``ellipsis()``
39 """``ellipsis()``
41 Changesets that are ellipsis nodes.
40 Changesets that are ellipsis nodes.
42 """
41 """
43 return subset.filter(lambda r: _isellipsis(repo, r))
42 return subset.filter(lambda r: _isellipsis(repo, r))
44
43
45 def setup():
44 def setup():
46 templatekw.keywords['ellipsis'] = ellipsis
45 templatekw.keywords['ellipsis'] = ellipsis
47 templatekw.keywords['outsidenarrow'] = outsidenarrow
46 templatekw.keywords['outsidenarrow'] = outsidenarrow
48
47
49 revset.symbols['ellipsis'] = ellipsisrevset
48 revset.symbols['ellipsis'] = ellipsisrevset
50 revset.safesymbols.add('ellipsis')
49 revset.safesymbols.add('ellipsis')
General Comments 0
You need to be logged in to leave comments. Login now