Show More
@@ -25,12 +25,12 b' import heapq' | |||
|
25 | 25 | CHANGESET = 'C' |
|
26 | 26 | |
|
27 | 27 | def groupbranchiter(revs, parentsfunc, firstbranch=()): |
|
28 |
""" |
|
|
28 | """Yield revisions from heads to roots one (topo) branch at a time. | |
|
29 | 29 | |
|
30 | 30 | This function aims to be used by a graph generator that wishes to minimize |
|
31 |
the |
|
|
31 | the number of parallel branches and their interleaving. | |
|
32 | 32 | |
|
33 | Example iteration order: | |
|
33 | Example iteration order (numbers show the "true" order in a changelog): | |
|
34 | 34 | |
|
35 | 35 | o 4 |
|
36 | 36 | | |
@@ -42,49 +42,51 b' def groupbranchiter(revs, parentsfunc, f' | |||
|
42 | 42 | |/ |
|
43 | 43 | o 0 |
|
44 | 44 | |
|
45 | Currently consider every changeset under a merge to be on the same branch | |
|
46 | using revision number to sort them. | |
|
45 | Note that the ancestors of merges are understood by the current | |
|
46 | algorithm to be on the same branch. This means no reordering will | |
|
47 | occur behind a merge. | |
|
47 | 48 | """ |
|
48 | 49 | |
|
49 | 50 | ### Quick summary of the algorithm |
|
50 | 51 | # |
|
51 | 52 | # This function is based around a "retention" principle. We keep revisions |
|
52 | 53 | # in memory until we are ready to emit a whole branch that immediately |
|
53 |
# "merge" into an existing one. This reduce the number of |
|
|
54 | # at the same time. | |
|
54 | # "merges" into an existing one. This reduces the number of parallel | |
|
55 | # branches with interleaved revisions. | |
|
55 | 56 | # |
|
56 | 57 | # During iteration revs are split into two groups: |
|
57 | 58 | # A) revision already emitted |
|
58 | 59 | # B) revision in "retention". They are stored as different subgroups. |
|
59 | 60 | # |
|
60 | # for each REV, we do the follow logic: | |
|
61 | # for each REV, we do the following logic: | |
|
61 | 62 | # |
|
62 |
# |
|
|
63 | # we'll "free" all the revs from subgroup in (B) that were waiting for | |
|
64 |
# |
|
|
65 | # emitting REV | |
|
63 | # 1) if REV is a parent of (A), we will emit it. If there is a | |
|
64 | # retention group ((B) above) that is blocked on REV being | |
|
65 | # available, we emit all the revisions out of that retention | |
|
66 | # group first. | |
|
66 | 67 | # |
|
67 |
# |
|
|
68 | # 2) else, we'll search for a subgroup in (B) awaiting for REV to be | |
|
68 | 69 | # available, if such subgroup exist, we add REV to it and the subgroup is |
|
69 | 70 | # now awaiting for REV.parents() to be available. |
|
70 | 71 | # |
|
71 |
# |
|
|
72 | # 3) finally if no such group existed in (B), we create a new subgroup. | |
|
72 | 73 | # |
|
73 | 74 | # |
|
74 |
# To bootstrap the algorithm, we emit the tipmost revision |
|
|
75 | # To bootstrap the algorithm, we emit the tipmost revision (which | |
|
76 | # puts it in group (A) from above). | |
|
75 | 77 | |
|
76 | 78 | revs.sort(reverse=True) |
|
77 | 79 | |
|
78 |
# Set of parents of revision that have been |
|
|
80 | # Set of parents of revision that have been emitted. They can be considered | |
|
79 | 81 | # unblocked as the graph generator is already aware of them so there is no |
|
80 |
# need to delay the |
|
|
82 | # need to delay the revisions that reference them. | |
|
81 | 83 | # |
|
82 | 84 | # If someone wants to prioritize a branch over the others, pre-filling this |
|
83 | 85 | # set will force all other branches to wait until this branch is ready to be |
|
84 |
# |
|
|
86 | # emitted. | |
|
85 | 87 | unblocked = set(firstbranch) |
|
86 | 88 | |
|
87 | # list of group waiting to be displayed, each group is defined by: | |
|
89 | # list of groups waiting to be displayed, each group is defined by: | |
|
88 | 90 | # |
|
89 | 91 | # (revs: lists of revs waiting to be displayed, |
|
90 | 92 | # blocked: set of that cannot be displayed before those in 'revs') |
@@ -93,15 +95,15 b' def groupbranchiter(revs, parentsfunc, f' | |||
|
93 | 95 | # group ('revs') that is not itself contained in the group. The main idea |
|
94 | 96 | # of this algorithm is to delay as much as possible the emission of any |
|
95 | 97 | # revision. This means waiting for the moment we are about to display |
|
96 |
# these |
|
|
98 | # these parents to display the revs in a group. | |
|
97 | 99 | # |
|
98 |
# This first implementation is smart until it |
|
|
99 |
# revs as soon as any parent |
|
|
100 | # This first implementation is smart until it encounters a merge: it will | |
|
101 | # emit revs as soon as any parent is about to be emitted and can grow an | |
|
100 | 102 | # arbitrary number of revs in 'blocked'. In practice this mean we properly |
|
101 |
# retains new branches but give up on any special ordering for ancestors |
|
|
102 | # merges. The implementation can be improved to handle this better. | |
|
103 | # retains new branches but gives up on any special ordering for ancestors | |
|
104 | # of merges. The implementation can be improved to handle this better. | |
|
103 | 105 | # |
|
104 | # The first subgroup is special. It correspond to all the revision that | |
|
106 | # The first subgroup is special. It corresponds to all the revision that | |
|
105 | 107 | # were already emitted. The 'revs' lists is expected to be empty and the |
|
106 | 108 | # 'blocked' set contains the parents revisions of already emitted revision. |
|
107 | 109 | # |
@@ -130,26 +132,34 b' def groupbranchiter(revs, parentsfunc, f' | |||
|
130 | 132 | matching = [i for i, g in enumerate(groups) if rev in g[1]] |
|
131 | 133 | |
|
132 | 134 | if matching: |
|
133 |
# The main idea is to gather together all sets that a |
|
|
134 | # the same revision. | |
|
135 | # The main idea is to gather together all sets that are blocked | |
|
136 | # on the same revision. | |
|
137 | # | |
|
138 | # Groups are merged when a common blocking ancestor is | |
|
139 | # observed. For example, given two groups: | |
|
135 | 140 | # |
|
136 | # This merging is done at the time we are about to add this | |
|
137 | # common awaited to the subgroup for simplicity purpose. Such | |
|
138 | # merge could happen sooner when we update the "blocked" set of | |
|
139 | # revision. | |
|
141 | # revs [5, 4] waiting for 1 | |
|
142 | # revs [3, 2] waiting for 1 | |
|
143 | # | |
|
144 | # These two groups will be merged when we process | |
|
145 | # 1. In theory, we could have merged the groups when | |
|
146 | # we added 2 to the group it is now in (we could have | |
|
147 | # noticed the groups were both blocked on 1 then), but | |
|
148 | # the way it works now makes the algorithm simpler. | |
|
140 | 149 | # |
|
141 | 150 | # We also always keep the oldest subgroup first. We can |
|
142 |
# probably improve the behavior by having the long |
|
|
143 |
# first. That way, graph algor |
|
|
144 |
# of parallel |
|
|
151 | # probably improve the behavior by having the longest set | |
|
152 | # first. That way, graph algorithms could minimise the length | |
|
153 | # of parallel lines their drawing. This is currently not done. | |
|
145 | 154 | targetidx = matching.pop(0) |
|
146 | 155 | trevs, tparents = groups[targetidx] |
|
147 | 156 | for i in matching: |
|
148 | 157 | gr = groups[i] |
|
149 | 158 | trevs.extend(gr[0]) |
|
150 | 159 | tparents |= gr[1] |
|
151 |
# delete all merged subgroups ( |
|
|
152 |
# from the last subgroup for performance and |
|
|
160 | # delete all merged subgroups (except the one we kept) | |
|
161 | # (starting from the last subgroup for performance and | |
|
162 | # sanity reasons) | |
|
153 | 163 | for i in reversed(matching): |
|
154 | 164 | del groups[i] |
|
155 | 165 | else: |
@@ -159,11 +169,11 b' def groupbranchiter(revs, parentsfunc, f' | |||
|
159 | 169 | |
|
160 | 170 | gr = groups[targetidx] |
|
161 | 171 | |
|
162 |
# We now add |
|
|
172 | # We now add the current nodes to this subgroups. This is done | |
|
163 | 173 | # after the subgroup merging because all elements from a subgroup |
|
164 |
# that relied on this rev must prece |
|
|
174 | # that relied on this rev must precede it. | |
|
165 | 175 | # |
|
166 |
# we also update the <parents> set to include |
|
|
176 | # we also update the <parents> set to include the parents of the | |
|
167 | 177 | # new nodes. |
|
168 | 178 | if rev == currentrev: # only display stuff in rev |
|
169 | 179 | gr[0].append(rev) |
@@ -177,15 +187,15 b' def groupbranchiter(revs, parentsfunc, f' | |||
|
177 | 187 | |
|
178 | 188 | # Look for a subgroup to display |
|
179 | 189 | # |
|
180 |
# When unblocked is empty (if clause), |
|
|
181 | # revision during the first iteration (if no priority was given) or | |
|
182 |
# if we |
|
|
183 |
# root). In that case we arbitrarily take |
|
|
184 |
# subgroup. The heuristi |
|
|
190 | # When unblocked is empty (if clause), we were not waiting for any | |
|
191 | # revisions during the first iteration (if no priority was given) or | |
|
192 | # if we emitted a whole disconnected set of the graph (reached a | |
|
193 | # root). In that case we arbitrarily take the oldest known | |
|
194 | # subgroup. The heuristic could probably be better. | |
|
185 | 195 | # |
|
186 |
# Otherwise (elif clause) |
|
|
187 | # if the subgroup awaits on the same revision that the outputed | |
|
188 | # ones, we can safely output it. | |
|
196 | # Otherwise (elif clause) if the subgroup is blocked on | |
|
197 | # a revision we just emitted, we can safely emit it as | |
|
198 | # well. | |
|
189 | 199 | if not unblocked: |
|
190 | 200 | if len(groups) > 1: # display other subset |
|
191 | 201 | targetidx = 1 |
@@ -206,7 +216,7 b' def groupbranchiter(revs, parentsfunc, f' | |||
|
206 | 216 | del groups[targetidx] |
|
207 | 217 | else: |
|
208 | 218 | gr[0][:] = [] |
|
209 | # Check if we have some subgroup waiting for revision we are not going to | |
|
219 | # Check if we have some subgroup waiting for revisions we are not going to | |
|
210 | 220 | # iterate over |
|
211 | 221 | for g in groups: |
|
212 | 222 | for r in g[0]: |
General Comments 0
You need to be logged in to leave comments.
Login now