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