##// END OF EJS Templates
exchange: move _computeellipsis() from narrow...
Gregory Szorc -
r38827:7e66e799 default
parent child Browse files
Show More
@@ -7,7 +7,6 b''
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
@@ -15,12 +14,10 b' 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,
@@ -52,131 +49,6 b' def getrepocaps_narrow(orig, repo, **kwa'
52 caps[NARROWCAP] = ['v0']
49 caps[NARROWCAP] = ['v0']
53 return caps
50 return caps
54
51
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
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'):
@@ -300,7 +172,7 b' def getbundlechangegrouppart_narrow(bund'
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(
@@ -311,7 +183,7 b' def getbundlechangegrouppart_narrow(bund'
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))
@@ -15,6 +15,7 b' from .node import ('
15 bin,
15 bin,
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 )
19 )
19 from .thirdparty import (
20 from .thirdparty import (
20 attr,
21 attr,
@@ -23,6 +24,7 b' from . import ('
23 bookmarks as bookmod,
24 bookmarks as bookmod,
24 bundle2,
25 bundle2,
25 changegroup,
26 changegroup,
27 dagutil,
26 discovery,
28 discovery,
27 error,
29 error,
28 lock as lockmod,
30 lock as lockmod,
@@ -1875,6 +1877,131 b' def applynarrowacl(repo, kwargs):'
1875 new_args['excludepats'] = req_excludes
1877 new_args['excludepats'] = req_excludes
1876 return new_args
1878 return new_args
1877
1879
1880 def _computeellipsis(repo, common, heads, known, match, depth=None):
1881 """Compute the shape of a narrowed DAG.
1882
1883 Args:
1884 repo: The repository we're transferring.
1885 common: The roots of the DAG range we're transferring.
1886 May be just [nullid], which means all ancestors of heads.
1887 heads: The heads of the DAG range we're transferring.
1888 match: The narrowmatcher that allows us to identify relevant changes.
1889 depth: If not None, only consider nodes to be full nodes if they are at
1890 most depth changesets away from one of heads.
1891
1892 Returns:
1893 A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
1894
1895 visitnodes: The list of nodes (either full or ellipsis) which
1896 need to be sent to the client.
1897 relevant_nodes: The set of changelog nodes which change a file inside
1898 the narrowspec. The client needs these as non-ellipsis nodes.
1899 ellipsisroots: A dict of {rev: parents} that is used in
1900 narrowchangegroup to produce ellipsis nodes with the
1901 correct parents.
1902 """
1903 cl = repo.changelog
1904 mfl = repo.manifestlog
1905
1906 cldag = dagutil.revlogdag(cl)
1907 # dagutil does not like nullid/nullrev
1908 commonrevs = cldag.internalizeall(common - set([nullid])) | set([nullrev])
1909 headsrevs = cldag.internalizeall(heads)
1910 if depth:
1911 revdepth = {h: 0 for h in headsrevs}
1912
1913 ellipsisheads = collections.defaultdict(set)
1914 ellipsisroots = collections.defaultdict(set)
1915
1916 def addroot(head, curchange):
1917 """Add a root to an ellipsis head, splitting heads with 3 roots."""
1918 ellipsisroots[head].add(curchange)
1919 # Recursively split ellipsis heads with 3 roots by finding the
1920 # roots' youngest common descendant which is an elided merge commit.
1921 # That descendant takes 2 of the 3 roots as its own, and becomes a
1922 # root of the head.
1923 while len(ellipsisroots[head]) > 2:
1924 child, roots = splithead(head)
1925 splitroots(head, child, roots)
1926 head = child # Recurse in case we just added a 3rd root
1927
1928 def splitroots(head, child, roots):
1929 ellipsisroots[head].difference_update(roots)
1930 ellipsisroots[head].add(child)
1931 ellipsisroots[child].update(roots)
1932 ellipsisroots[child].discard(child)
1933
1934 def splithead(head):
1935 r1, r2, r3 = sorted(ellipsisroots[head])
1936 for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
1937 mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
1938 nr1, head, nr2, head)
1939 for j in mid:
1940 if j == nr2:
1941 return nr2, (nr1, nr2)
1942 if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
1943 return j, (nr1, nr2)
1944 raise error.Abort(_('Failed to split up ellipsis node! head: %d, '
1945 'roots: %d %d %d') % (head, r1, r2, r3))
1946
1947 missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
1948 visit = reversed(missing)
1949 relevant_nodes = set()
1950 visitnodes = [cl.node(m) for m in missing]
1951 required = set(headsrevs) | known
1952 for rev in visit:
1953 clrev = cl.changelogrevision(rev)
1954 ps = cldag.parents(rev)
1955 if depth is not None:
1956 curdepth = revdepth[rev]
1957 for p in ps:
1958 revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
1959 needed = False
1960 shallow_enough = depth is None or revdepth[rev] <= depth
1961 if shallow_enough:
1962 curmf = mfl[clrev.manifest].read()
1963 if ps:
1964 # We choose to not trust the changed files list in
1965 # changesets because it's not always correct. TODO: could
1966 # we trust it for the non-merge case?
1967 p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
1968 needed = bool(curmf.diff(p1mf, match))
1969 if not needed and len(ps) > 1:
1970 # For merge changes, the list of changed files is not
1971 # helpful, since we need to emit the merge if a file
1972 # in the narrow spec has changed on either side of the
1973 # merge. As a result, we do a manifest diff to check.
1974 p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
1975 needed = bool(curmf.diff(p2mf, match))
1976 else:
1977 # For a root node, we need to include the node if any
1978 # files in the node match the narrowspec.
1979 needed = any(curmf.walk(match))
1980
1981 if needed:
1982 for head in ellipsisheads[rev]:
1983 addroot(head, rev)
1984 for p in ps:
1985 required.add(p)
1986 relevant_nodes.add(cl.node(rev))
1987 else:
1988 if not ps:
1989 ps = [nullrev]
1990 if rev in required:
1991 for head in ellipsisheads[rev]:
1992 addroot(head, rev)
1993 for p in ps:
1994 ellipsisheads[p].add(rev)
1995 else:
1996 for p in ps:
1997 ellipsisheads[p] |= ellipsisheads[rev]
1998
1999 # add common changesets as roots of their reachable ellipsis heads
2000 for c in commonrevs:
2001 for head in ellipsisheads[c]:
2002 addroot(head, c)
2003 return visitnodes, relevant_nodes, ellipsisroots
2004
1878 def caps20to10(repo, role):
2005 def caps20to10(repo, role):
1879 """return a set with appropriate options to use bundle20 during getbundle"""
2006 """return a set with appropriate options to use bundle20 during getbundle"""
1880 caps = {'HG20'}
2007 caps = {'HG20'}
General Comments 0
You need to be logged in to leave comments. Login now