hgext/narrow/narrowbundle2.py
changeset 38791 7e66e7999bdd
parent 38790 3e7387337a3c
child 38794 1d01cf0416a5
equal deleted inserted replaced
38790:3e7387337a3c 38791:7e66e7999bdd
     5 # This software may be used and distributed according to the terms of the
     5 # This software may be used and distributed according to the terms of the
     6 # GNU General Public License version 2 or any later version.
     6 # GNU General Public License version 2 or any later version.
     7 
     7 
     8 from __future__ import absolute_import
     8 from __future__ import absolute_import
     9 
     9 
    10 import collections
       
    11 import errno
    10 import errno
    12 import struct
    11 import struct
    13 
    12 
    14 from mercurial.i18n import _
    13 from mercurial.i18n import _
    15 from mercurial.node import (
    14 from mercurial.node import (
    16     bin,
    15     bin,
    17     nullid,
    16     nullid,
    18     nullrev,
       
    19 )
    17 )
    20 from mercurial import (
    18 from mercurial import (
    21     bundle2,
    19     bundle2,
    22     changegroup,
    20     changegroup,
    23     dagutil,
       
    24     error,
    21     error,
    25     exchange,
    22     exchange,
    26     extensions,
    23     extensions,
    27     narrowspec,
    24     narrowspec,
    28     repair,
    25     repair,
    49 # When advertising capabilities, always include narrow clone support.
    46 # When advertising capabilities, always include narrow clone support.
    50 def getrepocaps_narrow(orig, repo, **kwargs):
    47 def getrepocaps_narrow(orig, repo, **kwargs):
    51     caps = orig(repo, **kwargs)
    48     caps = orig(repo, **kwargs)
    52     caps[NARROWCAP] = ['v0']
    49     caps[NARROWCAP] = ['v0']
    53     return caps
    50     return caps
    54 
       
    55 def _computeellipsis(repo, common, heads, known, match, depth=None):
       
    56     """Compute the shape of a narrowed DAG.
       
    57 
       
    58     Args:
       
    59       repo: The repository we're transferring.
       
    60       common: The roots of the DAG range we're transferring.
       
    61               May be just [nullid], which means all ancestors of heads.
       
    62       heads: The heads of the DAG range we're transferring.
       
    63       match: The narrowmatcher that allows us to identify relevant changes.
       
    64       depth: If not None, only consider nodes to be full nodes if they are at
       
    65              most depth changesets away from one of heads.
       
    66 
       
    67     Returns:
       
    68       A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
       
    69 
       
    70         visitnodes: The list of nodes (either full or ellipsis) which
       
    71                     need to be sent to the client.
       
    72         relevant_nodes: The set of changelog nodes which change a file inside
       
    73                  the narrowspec. The client needs these as non-ellipsis nodes.
       
    74         ellipsisroots: A dict of {rev: parents} that is used in
       
    75                        narrowchangegroup to produce ellipsis nodes with the
       
    76                        correct parents.
       
    77     """
       
    78     cl = repo.changelog
       
    79     mfl = repo.manifestlog
       
    80 
       
    81     cldag = dagutil.revlogdag(cl)
       
    82     # dagutil does not like nullid/nullrev
       
    83     commonrevs = cldag.internalizeall(common - set([nullid])) | set([nullrev])
       
    84     headsrevs = cldag.internalizeall(heads)
       
    85     if depth:
       
    86         revdepth = {h: 0 for h in headsrevs}
       
    87 
       
    88     ellipsisheads = collections.defaultdict(set)
       
    89     ellipsisroots = collections.defaultdict(set)
       
    90 
       
    91     def addroot(head, curchange):
       
    92         """Add a root to an ellipsis head, splitting heads with 3 roots."""
       
    93         ellipsisroots[head].add(curchange)
       
    94         # Recursively split ellipsis heads with 3 roots by finding the
       
    95         # roots' youngest common descendant which is an elided merge commit.
       
    96         # That descendant takes 2 of the 3 roots as its own, and becomes a
       
    97         # root of the head.
       
    98         while len(ellipsisroots[head]) > 2:
       
    99             child, roots = splithead(head)
       
   100             splitroots(head, child, roots)
       
   101             head = child  # Recurse in case we just added a 3rd root
       
   102 
       
   103     def splitroots(head, child, roots):
       
   104         ellipsisroots[head].difference_update(roots)
       
   105         ellipsisroots[head].add(child)
       
   106         ellipsisroots[child].update(roots)
       
   107         ellipsisroots[child].discard(child)
       
   108 
       
   109     def splithead(head):
       
   110         r1, r2, r3 = sorted(ellipsisroots[head])
       
   111         for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
       
   112             mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
       
   113                             nr1, head, nr2, head)
       
   114             for j in mid:
       
   115                 if j == nr2:
       
   116                     return nr2, (nr1, nr2)
       
   117                 if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
       
   118                     return j, (nr1, nr2)
       
   119         raise error.Abort('Failed to split up ellipsis node! head: %d, '
       
   120                           'roots: %d %d %d' % (head, r1, r2, r3))
       
   121 
       
   122     missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
       
   123     visit = reversed(missing)
       
   124     relevant_nodes = set()
       
   125     visitnodes = [cl.node(m) for m in missing]
       
   126     required = set(headsrevs) | known
       
   127     for rev in visit:
       
   128         clrev = cl.changelogrevision(rev)
       
   129         ps = cldag.parents(rev)
       
   130         if depth is not None:
       
   131             curdepth = revdepth[rev]
       
   132             for p in ps:
       
   133                 revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
       
   134         needed = False
       
   135         shallow_enough = depth is None or revdepth[rev] <= depth
       
   136         if shallow_enough:
       
   137             curmf = mfl[clrev.manifest].read()
       
   138             if ps:
       
   139                 # We choose to not trust the changed files list in
       
   140                 # changesets because it's not always correct. TODO: could
       
   141                 # we trust it for the non-merge case?
       
   142                 p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
       
   143                 needed = bool(curmf.diff(p1mf, match))
       
   144                 if not needed and len(ps) > 1:
       
   145                     # For merge changes, the list of changed files is not
       
   146                     # helpful, since we need to emit the merge if a file
       
   147                     # in the narrow spec has changed on either side of the
       
   148                     # merge. As a result, we do a manifest diff to check.
       
   149                     p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
       
   150                     needed = bool(curmf.diff(p2mf, match))
       
   151             else:
       
   152                 # For a root node, we need to include the node if any
       
   153                 # files in the node match the narrowspec.
       
   154                 needed = any(curmf.walk(match))
       
   155 
       
   156         if needed:
       
   157             for head in ellipsisheads[rev]:
       
   158                 addroot(head, rev)
       
   159             for p in ps:
       
   160                 required.add(p)
       
   161             relevant_nodes.add(cl.node(rev))
       
   162         else:
       
   163             if not ps:
       
   164                 ps = [nullrev]
       
   165             if rev in required:
       
   166                 for head in ellipsisheads[rev]:
       
   167                     addroot(head, rev)
       
   168                 for p in ps:
       
   169                     ellipsisheads[p].add(rev)
       
   170             else:
       
   171                 for p in ps:
       
   172                     ellipsisheads[p] |= ellipsisheads[rev]
       
   173 
       
   174     # add common changesets as roots of their reachable ellipsis heads
       
   175     for c in commonrevs:
       
   176         for head in ellipsisheads[c]:
       
   177             addroot(head, c)
       
   178     return visitnodes, relevant_nodes, ellipsisroots
       
   179 
    51 
   180 def _packellipsischangegroup(repo, common, match, relevant_nodes,
    52 def _packellipsischangegroup(repo, common, match, relevant_nodes,
   181                              ellipsisroots, visitnodes, depth, source, version):
    53                              ellipsisroots, visitnodes, depth, source, version):
   182     if version in ('01', '02'):
    54     if version in ('01', '02'):
   183         raise error.Abort(
    55         raise error.Abort(
   298             for r in deadrevs:
   170             for r in deadrevs:
   299                 yield _KILLNODESIGNAL
   171                 yield _KILLNODESIGNAL
   300                 yield repo.changelog.node(r)
   172                 yield repo.changelog.node(r)
   301             yield _DONESIGNAL
   173             yield _DONESIGNAL
   302         bundler.newpart(_CHANGESPECPART, data=genkills())
   174         bundler.newpart(_CHANGESPECPART, data=genkills())
   303         newvisit, newfull, newellipsis = _computeellipsis(
   175         newvisit, newfull, newellipsis = exchange._computeellipsis(
   304             repo, set(), common, known, newmatch)
   176             repo, set(), common, known, newmatch)
   305         if newvisit:
   177         if newvisit:
   306             cg = _packellipsischangegroup(
   178             cg = _packellipsischangegroup(
   307                 repo, common, newmatch, newfull, newellipsis,
   179                 repo, common, newmatch, newfull, newellipsis,
   308                 newvisit, depth, source, version)
   180                 newvisit, depth, source, version)
   309             part = bundler.newpart('changegroup', data=cg)
   181             part = bundler.newpart('changegroup', data=cg)
   310             part.addparam('version', version)
   182             part.addparam('version', version)
   311             if 'treemanifest' in repo.requirements:
   183             if 'treemanifest' in repo.requirements:
   312                 part.addparam('treemanifest', '1')
   184                 part.addparam('treemanifest', '1')
   313 
   185 
   314     visitnodes, relevant_nodes, ellipsisroots = _computeellipsis(
   186     visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
   315         repo, common, heads, set(), newmatch, depth=depth)
   187         repo, common, heads, set(), newmatch, depth=depth)
   316 
   188 
   317     repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
   189     repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
   318     if visitnodes:
   190     if visitnodes:
   319         cg = _packellipsischangegroup(
   191         cg = _packellipsischangegroup(