Narrow had a custom version of generate() that was essentially a copy
of generate() with inline additions to facilitate ellipses serving.
This commit inlines those modifications into generate().
durin42 |
hg-reviewers |
Narrow had a custom version of generate() that was essentially a copy
of generate() with inline additions to facilitate ellipses serving.
This commit inlines those modifications into generate().
Automatic diff as part of commit; lint not applicable. |
Automatic diff as part of commit; unit tests not applicable. |
Path | Packages | |||
---|---|---|---|---|
M | hgext/narrow/__init__.py (2 lines) | |||
D | M | hgext/narrow/narrowchangegroup.py (145 lines) | ||
M | mercurial/changegroup.py (63 lines) |
Status | Author | Revision | |
---|---|---|---|
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg | ||
Closed | indygreg |
localrepo, | localrepo, | ||||
registrar, | registrar, | ||||
repository, | repository, | ||||
verify as verifymod, | verify as verifymod, | ||||
) | ) | ||||
from . import ( | from . import ( | ||||
narrowbundle2, | narrowbundle2, | ||||
narrowchangegroup, | |||||
narrowcommands, | narrowcommands, | ||||
narrowcopies, | narrowcopies, | ||||
narrowpatch, | narrowpatch, | ||||
narrowrepo, | narrowrepo, | ||||
narrowrevlog, | narrowrevlog, | ||||
narrowtemplates, | narrowtemplates, | ||||
narrowwirepeer, | narrowwirepeer, | ||||
) | ) | ||||
features.add(repository.NARROW_REQUIREMENT) | features.add(repository.NARROW_REQUIREMENT) | ||||
def uisetup(ui): | def uisetup(ui): | ||||
"""Wraps user-facing mercurial commands with narrow-aware versions.""" | """Wraps user-facing mercurial commands with narrow-aware versions.""" | ||||
localrepo.featuresetupfuncs.add(featuresetup) | localrepo.featuresetupfuncs.add(featuresetup) | ||||
narrowrevlog.setup() | narrowrevlog.setup() | ||||
narrowbundle2.setup() | narrowbundle2.setup() | ||||
narrowcommands.setup() | narrowcommands.setup() | ||||
narrowchangegroup.setup() | |||||
narrowwirepeer.uisetup() | narrowwirepeer.uisetup() | ||||
def reposetup(ui, repo): | def reposetup(ui, repo): | ||||
"""Wraps local repositories with narrow repo support.""" | """Wraps local repositories with narrow repo support.""" | ||||
if not repo.local(): | if not repo.local(): | ||||
return | return | ||||
if repository.NARROW_REQUIREMENT in repo.requirements: | if repository.NARROW_REQUIREMENT in repo.requirements: |
# narrowchangegroup.py - narrow clone changegroup creation and consumption | |||||
# | |||||
# Copyright 2017 Google, Inc. | |||||
# | |||||
# This software may be used and distributed according to the terms of the | |||||
# GNU General Public License version 2 or any later version. | |||||
from __future__ import absolute_import | |||||
from mercurial.i18n import _ | |||||
from mercurial import ( | |||||
changegroup, | |||||
extensions, | |||||
node, | |||||
util, | |||||
) | |||||
def setup(): | |||||
def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source): | |||||
'''yield a sequence of changegroup chunks (strings)''' | |||||
# Note: other than delegating to orig, the only deviation in | |||||
# logic from normal hg's generate is marked with BEGIN/END | |||||
# NARROW HACK. | |||||
if not util.safehasattr(self, 'full_nodes'): | |||||
# not sending a narrow bundle | |||||
for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source): | |||||
yield x | |||||
return | |||||
repo = self._repo | |||||
cl = repo.changelog | |||||
mfl = repo.manifestlog | |||||
mfrevlog = mfl._revlog | |||||
clrevorder = {} | |||||
mfs = {} # needed manifests | |||||
fnodes = {} # needed file nodes | |||||
changedfiles = set() | |||||
# Callback for the changelog, used to collect changed files and manifest | |||||
# nodes. | |||||
# Returns the linkrev node (identity in the changelog case). | |||||
def lookupcl(x): | |||||
c = cl.read(x) | |||||
clrevorder[x] = len(clrevorder) | |||||
# BEGIN NARROW HACK | |||||
# | |||||
# Only update mfs if x is going to be sent. Otherwise we | |||||
# end up with bogus linkrevs specified for manifests and | |||||
# we skip some manifest nodes that we should otherwise | |||||
# have sent. | |||||
if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis: | |||||
n = c[0] | |||||
# record the first changeset introducing this manifest version | |||||
mfs.setdefault(n, x) | |||||
# Set this narrow-specific dict so we have the lowest manifest | |||||
# revnum to look up for this cl revnum. (Part of mapping | |||||
# changelog ellipsis parents to manifest ellipsis parents) | |||||
self.next_clrev_to_localrev.setdefault(cl.rev(x), | |||||
mfrevlog.rev(n)) | |||||
# We can't trust the changed files list in the changeset if the | |||||
# client requested a shallow clone. | |||||
if self.is_shallow: | |||||
changedfiles.update(mfl[c[0]].read().keys()) | |||||
else: | |||||
changedfiles.update(c[3]) | |||||
# END NARROW HACK | |||||
# Record a complete list of potentially-changed files in | |||||
# this manifest. | |||||
return x | |||||
self._verbosenote(_('uncompressed size of bundle content:\n')) | |||||
size = 0 | |||||
for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')): | |||||
size += len(chunk) | |||||
yield chunk | |||||
self._verbosenote(_('%8.i (changelog)\n') % size) | |||||
# We need to make sure that the linkrev in the changegroup refers to | |||||
# the first changeset that introduced the manifest or file revision. | |||||
# The fastpath is usually safer than the slowpath, because the filelogs | |||||
# are walked in revlog order. | |||||
# | |||||
# When taking the slowpath with reorder=None and the manifest revlog | |||||
# uses generaldelta, the manifest may be walked in the "wrong" order. | |||||
# Without 'clrevorder', we would get an incorrect linkrev (see fix in | |||||
# cc0ff93d0c0c). | |||||
# | |||||
# When taking the fastpath, we are only vulnerable to reordering | |||||
# of the changelog itself. The changelog never uses generaldelta, so | |||||
# it is only reordered when reorder=True. To handle this case, we | |||||
# simply take the slowpath, which already has the 'clrevorder' logic. | |||||
# This was also fixed in cc0ff93d0c0c. | |||||
fastpathlinkrev = fastpathlinkrev and not self._reorder | |||||
# Treemanifests don't work correctly with fastpathlinkrev | |||||
# either, because we don't discover which directory nodes to | |||||
# send along with files. This could probably be fixed. | |||||
fastpathlinkrev = fastpathlinkrev and ( | |||||
'treemanifest' not in repo.requirements) | |||||
# Shallow clones also don't work correctly with fastpathlinkrev | |||||
# because file nodes may need to be sent for a manifest even if they | |||||
# weren't introduced by that manifest. | |||||
fastpathlinkrev = fastpathlinkrev and not self.is_shallow | |||||
for chunk in self.generatemanifests(commonrevs, clrevorder, | |||||
fastpathlinkrev, mfs, fnodes, source): | |||||
yield chunk | |||||
# BEGIN NARROW HACK | |||||
mfdicts = None | |||||
if self.is_shallow: | |||||
mfdicts = [(self._repo.manifestlog[n].read(), lr) | |||||
for (n, lr) in mfs.iteritems()] | |||||
# END NARROW HACK | |||||
mfs.clear() | |||||
clrevs = set(cl.rev(x) for x in clnodes) | |||||
if not fastpathlinkrev: | |||||
def linknodes(unused, fname): | |||||
return fnodes.get(fname, {}) | |||||
else: | |||||
cln = cl.node | |||||
def linknodes(filerevlog, fname): | |||||
llr = filerevlog.linkrev | |||||
fln = filerevlog.node | |||||
revs = ((r, llr(r)) for r in filerevlog) | |||||
return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs) | |||||
# BEGIN NARROW HACK | |||||
# | |||||
# We need to pass the mfdicts variable down into | |||||
# generatefiles(), but more than one command might have | |||||
# wrapped generatefiles so we can't modify the function | |||||
# signature. Instead, we pass the data to ourselves using an | |||||
# instance attribute. I'm sorry. | |||||
self._mfdicts = mfdicts | |||||
# END NARROW HACK | |||||
for chunk in self.generatefiles(changedfiles, linknodes, commonrevs, | |||||
source): | |||||
yield chunk | |||||
yield self.close() | |||||
if clnodes: | |||||
repo.hook('outgoing', node=node.hex(clnodes[0]), source=source) | |||||
extensions.wrapfunction(changegroup.cg1packer, 'generate', generate) |
def generate(self, commonrevs, clnodes, fastpathlinkrev, source): | def generate(self, commonrevs, clnodes, fastpathlinkrev, source): | ||||
'''yield a sequence of changegroup chunks (strings)''' | '''yield a sequence of changegroup chunks (strings)''' | ||||
repo = self._repo | repo = self._repo | ||||
cl = repo.changelog | cl = repo.changelog | ||||
clrevorder = {} | clrevorder = {} | ||||
mfs = {} # needed manifests | mfs = {} # needed manifests | ||||
fnodes = {} # needed file nodes | fnodes = {} # needed file nodes | ||||
mfl = repo.manifestlog | |||||
# TODO violates storage abstraction. | |||||
mfrevlog = mfl._revlog | |||||
changedfiles = set() | changedfiles = set() | ||||
# Callback for the changelog, used to collect changed files and manifest | ellipsesmode = util.safehasattr(self, 'full_nodes') | ||||
# nodes. | |||||
# Callback for the changelog, used to collect changed files and | |||||
# manifest nodes. | |||||
# Returns the linkrev node (identity in the changelog case). | # Returns the linkrev node (identity in the changelog case). | ||||
def lookupcl(x): | def lookupcl(x): | ||||
c = cl.read(x) | c = cl.read(x) | ||||
clrevorder[x] = len(clrevorder) | clrevorder[x] = len(clrevorder) | ||||
if ellipsesmode: | |||||
# Only update mfs if x is going to be sent. Otherwise we | |||||
# end up with bogus linkrevs specified for manifests and | |||||
# we skip some manifest nodes that we should otherwise | |||||
# have sent. | |||||
if (x in self.full_nodes | |||||
or cl.rev(x) in self.precomputed_ellipsis): | |||||
n = c[0] | |||||
# Record the first changeset introducing this manifest | |||||
# version. | |||||
mfs.setdefault(n, x) | |||||
# Set this narrow-specific dict so we have the lowest | |||||
# manifest revnum to look up for this cl revnum. (Part of | |||||
# mapping changelog ellipsis parents to manifest ellipsis | |||||
# parents) | |||||
self.next_clrev_to_localrev.setdefault(cl.rev(x), | |||||
mfrevlog.rev(n)) | |||||
# We can't trust the changed files list in the changeset if the | |||||
# client requested a shallow clone. | |||||
if self.is_shallow: | |||||
changedfiles.update(mfl[c[0]].read().keys()) | |||||
else: | |||||
changedfiles.update(c[3]) | |||||
else: | |||||
n = c[0] | n = c[0] | ||||
# record the first changeset introducing this manifest version | # record the first changeset introducing this manifest version | ||||
mfs.setdefault(n, x) | mfs.setdefault(n, x) | ||||
# Record a complete list of potentially-changed files in | # Record a complete list of potentially-changed files in | ||||
# this manifest. | # this manifest. | ||||
changedfiles.update(c[3]) | changedfiles.update(c[3]) | ||||
return x | return x | ||||
self._verbosenote(_('uncompressed size of bundle content:\n')) | self._verbosenote(_('uncompressed size of bundle content:\n')) | ||||
size = 0 | size = 0 | ||||
for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')): | for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')): | ||||
size += len(chunk) | size += len(chunk) | ||||
yield chunk | yield chunk | ||||
self._verbosenote(_('%8.i (changelog)\n') % size) | self._verbosenote(_('%8.i (changelog)\n') % size) | ||||
# either, because we don't discover which directory nodes to | # either, because we don't discover which directory nodes to | ||||
# send along with files. This could probably be fixed. | # send along with files. This could probably be fixed. | ||||
fastpathlinkrev = fastpathlinkrev and ( | fastpathlinkrev = fastpathlinkrev and ( | ||||
'treemanifest' not in repo.requirements) | 'treemanifest' not in repo.requirements) | ||||
for chunk in self.generatemanifests(commonrevs, clrevorder, | for chunk in self.generatemanifests(commonrevs, clrevorder, | ||||
fastpathlinkrev, mfs, fnodes, source): | fastpathlinkrev, mfs, fnodes, source): | ||||
yield chunk | yield chunk | ||||
if ellipsesmode: | |||||
mfdicts = None | |||||
if self.is_shallow: | |||||
mfdicts = [(self._repo.manifestlog[n].read(), lr) | |||||
for (n, lr) in mfs.iteritems()] | |||||
mfs.clear() | mfs.clear() | ||||
clrevs = set(cl.rev(x) for x in clnodes) | clrevs = set(cl.rev(x) for x in clnodes) | ||||
if not fastpathlinkrev: | if not fastpathlinkrev: | ||||
def linknodes(unused, fname): | def linknodes(unused, fname): | ||||
return fnodes.get(fname, {}) | return fnodes.get(fname, {}) | ||||
else: | else: | ||||
cln = cl.node | cln = cl.node | ||||
def linknodes(filerevlog, fname): | def linknodes(filerevlog, fname): | ||||
llr = filerevlog.linkrev | llr = filerevlog.linkrev | ||||
fln = filerevlog.node | fln = filerevlog.node | ||||
revs = ((r, llr(r)) for r in filerevlog) | revs = ((r, llr(r)) for r in filerevlog) | ||||
return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs) | return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs) | ||||
if ellipsesmode: | |||||
# We need to pass the mfdicts variable down into | |||||
# generatefiles(), but more than one command might have | |||||
# wrapped generatefiles so we can't modify the function | |||||
# signature. Instead, we pass the data to ourselves using an | |||||
# instance attribute. I'm sorry. | |||||
self._mfdicts = mfdicts | |||||
for chunk in self.generatefiles(changedfiles, linknodes, commonrevs, | for chunk in self.generatefiles(changedfiles, linknodes, commonrevs, | ||||
source): | source): | ||||
yield chunk | yield chunk | ||||
yield self.close() | yield self.close() | ||||
if clnodes: | if clnodes: | ||||
repo.hook('outgoing', node=hex(clnodes[0]), source=source) | repo.hook('outgoing', node=hex(clnodes[0]), source=source) |