##// END OF EJS Templates
revset: move groupbranchiter over from graphmod...
Martijn Pieters -
r29347:98535ad4 default
parent child Browse files
Show More
@@ -19,8 +19,6 b' Data depends on type.'
19
19
20 from __future__ import absolute_import
20 from __future__ import absolute_import
21
21
22 import heapq
23
24 from .node import nullrev
22 from .node import nullrev
25 from . import (
23 from . import (
26 revset,
24 revset,
@@ -37,204 +35,6 b" MISSINGPARENT = 'M'"
37 # (so making N negative) and all but the first N characters use that style.
35 # (so making N negative) and all but the first N characters use that style.
38 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
36 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
39
37
40 def groupbranchiter(revs, parentsfunc, firstbranch=()):
41 """Yield revisions from heads to roots one (topo) branch at a time.
42
43 This function aims to be used by a graph generator that wishes to minimize
44 the number of parallel branches and their interleaving.
45
46 Example iteration order (numbers show the "true" order in a changelog):
47
48 o 4
49 |
50 o 1
51 |
52 | o 3
53 | |
54 | o 2
55 |/
56 o 0
57
58 Note that the ancestors of merges are understood by the current
59 algorithm to be on the same branch. This means no reordering will
60 occur behind a merge.
61 """
62
63 ### Quick summary of the algorithm
64 #
65 # This function is based around a "retention" principle. We keep revisions
66 # in memory until we are ready to emit a whole branch that immediately
67 # "merges" into an existing one. This reduces the number of parallel
68 # branches with interleaved revisions.
69 #
70 # During iteration revs are split into two groups:
71 # A) revision already emitted
72 # B) revision in "retention". They are stored as different subgroups.
73 #
74 # for each REV, we do the following logic:
75 #
76 # 1) if REV is a parent of (A), we will emit it. If there is a
77 # retention group ((B) above) that is blocked on REV being
78 # available, we emit all the revisions out of that retention
79 # group first.
80 #
81 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
82 # available, if such subgroup exist, we add REV to it and the subgroup is
83 # now awaiting for REV.parents() to be available.
84 #
85 # 3) finally if no such group existed in (B), we create a new subgroup.
86 #
87 #
88 # To bootstrap the algorithm, we emit the tipmost revision (which
89 # puts it in group (A) from above).
90
91 revs.sort(reverse=True)
92
93 # Set of parents of revision that have been emitted. They can be considered
94 # unblocked as the graph generator is already aware of them so there is no
95 # need to delay the revisions that reference them.
96 #
97 # If someone wants to prioritize a branch over the others, pre-filling this
98 # set will force all other branches to wait until this branch is ready to be
99 # emitted.
100 unblocked = set(firstbranch)
101
102 # list of groups waiting to be displayed, each group is defined by:
103 #
104 # (revs: lists of revs waiting to be displayed,
105 # blocked: set of that cannot be displayed before those in 'revs')
106 #
107 # The second value ('blocked') correspond to parents of any revision in the
108 # group ('revs') that is not itself contained in the group. The main idea
109 # of this algorithm is to delay as much as possible the emission of any
110 # revision. This means waiting for the moment we are about to display
111 # these parents to display the revs in a group.
112 #
113 # This first implementation is smart until it encounters a merge: it will
114 # emit revs as soon as any parent is about to be emitted and can grow an
115 # arbitrary number of revs in 'blocked'. In practice this mean we properly
116 # retains new branches but gives up on any special ordering for ancestors
117 # of merges. The implementation can be improved to handle this better.
118 #
119 # The first subgroup is special. It corresponds to all the revision that
120 # were already emitted. The 'revs' lists is expected to be empty and the
121 # 'blocked' set contains the parents revisions of already emitted revision.
122 #
123 # You could pre-seed the <parents> set of groups[0] to a specific
124 # changesets to select what the first emitted branch should be.
125 groups = [([], unblocked)]
126 pendingheap = []
127 pendingset = set()
128
129 heapq.heapify(pendingheap)
130 heappop = heapq.heappop
131 heappush = heapq.heappush
132 for currentrev in revs:
133 # Heap works with smallest element, we want highest so we invert
134 if currentrev not in pendingset:
135 heappush(pendingheap, -currentrev)
136 pendingset.add(currentrev)
137 # iterates on pending rev until after the current rev have been
138 # processed.
139 rev = None
140 while rev != currentrev:
141 rev = -heappop(pendingheap)
142 pendingset.remove(rev)
143
144 # Seek for a subgroup blocked, waiting for the current revision.
145 matching = [i for i, g in enumerate(groups) if rev in g[1]]
146
147 if matching:
148 # The main idea is to gather together all sets that are blocked
149 # on the same revision.
150 #
151 # Groups are merged when a common blocking ancestor is
152 # observed. For example, given two groups:
153 #
154 # revs [5, 4] waiting for 1
155 # revs [3, 2] waiting for 1
156 #
157 # These two groups will be merged when we process
158 # 1. In theory, we could have merged the groups when
159 # we added 2 to the group it is now in (we could have
160 # noticed the groups were both blocked on 1 then), but
161 # the way it works now makes the algorithm simpler.
162 #
163 # We also always keep the oldest subgroup first. We can
164 # probably improve the behavior by having the longest set
165 # first. That way, graph algorithms could minimise the length
166 # of parallel lines their drawing. This is currently not done.
167 targetidx = matching.pop(0)
168 trevs, tparents = groups[targetidx]
169 for i in matching:
170 gr = groups[i]
171 trevs.extend(gr[0])
172 tparents |= gr[1]
173 # delete all merged subgroups (except the one we kept)
174 # (starting from the last subgroup for performance and
175 # sanity reasons)
176 for i in reversed(matching):
177 del groups[i]
178 else:
179 # This is a new head. We create a new subgroup for it.
180 targetidx = len(groups)
181 groups.append(([], set([rev])))
182
183 gr = groups[targetidx]
184
185 # We now add the current nodes to this subgroups. This is done
186 # after the subgroup merging because all elements from a subgroup
187 # that relied on this rev must precede it.
188 #
189 # we also update the <parents> set to include the parents of the
190 # new nodes.
191 if rev == currentrev: # only display stuff in rev
192 gr[0].append(rev)
193 gr[1].remove(rev)
194 parents = [p for p in parentsfunc(rev) if p > nullrev]
195 gr[1].update(parents)
196 for p in parents:
197 if p not in pendingset:
198 pendingset.add(p)
199 heappush(pendingheap, -p)
200
201 # Look for a subgroup to display
202 #
203 # When unblocked is empty (if clause), we were not waiting for any
204 # revisions during the first iteration (if no priority was given) or
205 # if we emitted a whole disconnected set of the graph (reached a
206 # root). In that case we arbitrarily take the oldest known
207 # subgroup. The heuristic could probably be better.
208 #
209 # Otherwise (elif clause) if the subgroup is blocked on
210 # a revision we just emitted, we can safely emit it as
211 # well.
212 if not unblocked:
213 if len(groups) > 1: # display other subset
214 targetidx = 1
215 gr = groups[1]
216 elif not gr[1] & unblocked:
217 gr = None
218
219 if gr is not None:
220 # update the set of awaited revisions with the one from the
221 # subgroup
222 unblocked |= gr[1]
223 # output all revisions in the subgroup
224 for r in gr[0]:
225 yield r
226 # delete the subgroup that you just output
227 # unless it is groups[0] in which case you just empty it.
228 if targetidx:
229 del groups[targetidx]
230 else:
231 gr[0][:] = []
232 # Check if we have some subgroup waiting for revisions we are not going to
233 # iterate over
234 for g in groups:
235 for r in g[0]:
236 yield r
237
238 def dagwalker(repo, revs):
38 def dagwalker(repo, revs):
239 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
39 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
240
40
@@ -259,7 +59,7 b' def dagwalker(repo, revs):'
259 if firstbranchrevset:
59 if firstbranchrevset:
260 firstbranch = repo.revs(firstbranchrevset)
60 firstbranch = repo.revs(firstbranchrevset)
261 parentrevs = repo.changelog.parentrevs
61 parentrevs = repo.changelog.parentrevs
262 revs = groupbranchiter(revs, parentrevs, firstbranch)
62 revs = revset.groupbranchiter(revs, parentrevs, firstbranch)
263 revs = revset.baseset(revs)
63 revs = revset.baseset(revs)
264
64
265 for rev in revs:
65 for rev in revs:
@@ -1887,6 +1887,204 b' def sort(repo, subset, x):'
1887 raise error.ParseError(_("unknown sort key %r") % fk)
1887 raise error.ParseError(_("unknown sort key %r") % fk)
1888 return baseset([c.rev() for c in ctxs])
1888 return baseset([c.rev() for c in ctxs])
1889
1889
1890 def groupbranchiter(revs, parentsfunc, firstbranch=()):
1891 """Yield revisions from heads to roots one (topo) branch at a time.
1892
1893 This function aims to be used by a graph generator that wishes to minimize
1894 the number of parallel branches and their interleaving.
1895
1896 Example iteration order (numbers show the "true" order in a changelog):
1897
1898 o 4
1899 |
1900 o 1
1901 |
1902 | o 3
1903 | |
1904 | o 2
1905 |/
1906 o 0
1907
1908 Note that the ancestors of merges are understood by the current
1909 algorithm to be on the same branch. This means no reordering will
1910 occur behind a merge.
1911 """
1912
1913 ### Quick summary of the algorithm
1914 #
1915 # This function is based around a "retention" principle. We keep revisions
1916 # in memory until we are ready to emit a whole branch that immediately
1917 # "merges" into an existing one. This reduces the number of parallel
1918 # branches with interleaved revisions.
1919 #
1920 # During iteration revs are split into two groups:
1921 # A) revision already emitted
1922 # B) revision in "retention". They are stored as different subgroups.
1923 #
1924 # for each REV, we do the following logic:
1925 #
1926 # 1) if REV is a parent of (A), we will emit it. If there is a
1927 # retention group ((B) above) that is blocked on REV being
1928 # available, we emit all the revisions out of that retention
1929 # group first.
1930 #
1931 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
1932 # available, if such subgroup exist, we add REV to it and the subgroup is
1933 # now awaiting for REV.parents() to be available.
1934 #
1935 # 3) finally if no such group existed in (B), we create a new subgroup.
1936 #
1937 #
1938 # To bootstrap the algorithm, we emit the tipmost revision (which
1939 # puts it in group (A) from above).
1940
1941 revs.sort(reverse=True)
1942
1943 # Set of parents of revision that have been emitted. They can be considered
1944 # unblocked as the graph generator is already aware of them so there is no
1945 # need to delay the revisions that reference them.
1946 #
1947 # If someone wants to prioritize a branch over the others, pre-filling this
1948 # set will force all other branches to wait until this branch is ready to be
1949 # emitted.
1950 unblocked = set(firstbranch)
1951
1952 # list of groups waiting to be displayed, each group is defined by:
1953 #
1954 # (revs: lists of revs waiting to be displayed,
1955 # blocked: set of that cannot be displayed before those in 'revs')
1956 #
1957 # The second value ('blocked') correspond to parents of any revision in the
1958 # group ('revs') that is not itself contained in the group. The main idea
1959 # of this algorithm is to delay as much as possible the emission of any
1960 # revision. This means waiting for the moment we are about to display
1961 # these parents to display the revs in a group.
1962 #
1963 # This first implementation is smart until it encounters a merge: it will
1964 # emit revs as soon as any parent is about to be emitted and can grow an
1965 # arbitrary number of revs in 'blocked'. In practice this mean we properly
1966 # retains new branches but gives up on any special ordering for ancestors
1967 # of merges. The implementation can be improved to handle this better.
1968 #
1969 # The first subgroup is special. It corresponds to all the revision that
1970 # were already emitted. The 'revs' lists is expected to be empty and the
1971 # 'blocked' set contains the parents revisions of already emitted revision.
1972 #
1973 # You could pre-seed the <parents> set of groups[0] to a specific
1974 # changesets to select what the first emitted branch should be.
1975 groups = [([], unblocked)]
1976 pendingheap = []
1977 pendingset = set()
1978
1979 heapq.heapify(pendingheap)
1980 heappop = heapq.heappop
1981 heappush = heapq.heappush
1982 for currentrev in revs:
1983 # Heap works with smallest element, we want highest so we invert
1984 if currentrev not in pendingset:
1985 heappush(pendingheap, -currentrev)
1986 pendingset.add(currentrev)
1987 # iterates on pending rev until after the current rev have been
1988 # processed.
1989 rev = None
1990 while rev != currentrev:
1991 rev = -heappop(pendingheap)
1992 pendingset.remove(rev)
1993
1994 # Seek for a subgroup blocked, waiting for the current revision.
1995 matching = [i for i, g in enumerate(groups) if rev in g[1]]
1996
1997 if matching:
1998 # The main idea is to gather together all sets that are blocked
1999 # on the same revision.
2000 #
2001 # Groups are merged when a common blocking ancestor is
2002 # observed. For example, given two groups:
2003 #
2004 # revs [5, 4] waiting for 1
2005 # revs [3, 2] waiting for 1
2006 #
2007 # These two groups will be merged when we process
2008 # 1. In theory, we could have merged the groups when
2009 # we added 2 to the group it is now in (we could have
2010 # noticed the groups were both blocked on 1 then), but
2011 # the way it works now makes the algorithm simpler.
2012 #
2013 # We also always keep the oldest subgroup first. We can
2014 # probably improve the behavior by having the longest set
2015 # first. That way, graph algorithms could minimise the length
2016 # of parallel lines their drawing. This is currently not done.
2017 targetidx = matching.pop(0)
2018 trevs, tparents = groups[targetidx]
2019 for i in matching:
2020 gr = groups[i]
2021 trevs.extend(gr[0])
2022 tparents |= gr[1]
2023 # delete all merged subgroups (except the one we kept)
2024 # (starting from the last subgroup for performance and
2025 # sanity reasons)
2026 for i in reversed(matching):
2027 del groups[i]
2028 else:
2029 # This is a new head. We create a new subgroup for it.
2030 targetidx = len(groups)
2031 groups.append(([], set([rev])))
2032
2033 gr = groups[targetidx]
2034
2035 # We now add the current nodes to this subgroups. This is done
2036 # after the subgroup merging because all elements from a subgroup
2037 # that relied on this rev must precede it.
2038 #
2039 # we also update the <parents> set to include the parents of the
2040 # new nodes.
2041 if rev == currentrev: # only display stuff in rev
2042 gr[0].append(rev)
2043 gr[1].remove(rev)
2044 parents = [p for p in parentsfunc(rev) if p > node.nullrev]
2045 gr[1].update(parents)
2046 for p in parents:
2047 if p not in pendingset:
2048 pendingset.add(p)
2049 heappush(pendingheap, -p)
2050
2051 # Look for a subgroup to display
2052 #
2053 # When unblocked is empty (if clause), we were not waiting for any
2054 # revisions during the first iteration (if no priority was given) or
2055 # if we emitted a whole disconnected set of the graph (reached a
2056 # root). In that case we arbitrarily take the oldest known
2057 # subgroup. The heuristic could probably be better.
2058 #
2059 # Otherwise (elif clause) if the subgroup is blocked on
2060 # a revision we just emitted, we can safely emit it as
2061 # well.
2062 if not unblocked:
2063 if len(groups) > 1: # display other subset
2064 targetidx = 1
2065 gr = groups[1]
2066 elif not gr[1] & unblocked:
2067 gr = None
2068
2069 if gr is not None:
2070 # update the set of awaited revisions with the one from the
2071 # subgroup
2072 unblocked |= gr[1]
2073 # output all revisions in the subgroup
2074 for r in gr[0]:
2075 yield r
2076 # delete the subgroup that you just output
2077 # unless it is groups[0] in which case you just empty it.
2078 if targetidx:
2079 del groups[targetidx]
2080 else:
2081 gr[0][:] = []
2082 # Check if we have some subgroup waiting for revisions we are not going to
2083 # iterate over
2084 for g in groups:
2085 for r in g[0]:
2086 yield r
2087
1890 @predicate('subrepo([pattern])')
2088 @predicate('subrepo([pattern])')
1891 def subrepo(repo, subset, x):
2089 def subrepo(repo, subset, x):
1892 """Changesets that add, modify or remove the given subrepo. If no subrepo
2090 """Changesets that add, modify or remove the given subrepo. If no subrepo
General Comments 0
You need to be logged in to leave comments. Login now