94 else: |
94 else: |
95 to_write = oldcontent[idx] |
95 to_write = oldcontent[idx] |
96 yield to_write |
96 yield to_write |
97 |
97 |
98 |
98 |
99 def updatefile(filename, idx): |
99 def merge_content(base, left, right): |
100 """update <filename> to be at appropriate content for iteration <idx>""" |
100 """merge two file content to produce a new one |
101 existing = None |
101 |
102 if idx > 0: |
102 use unambiguous update on each side when possible, and produce a new line |
103 with open(filename, 'rb') as old: |
103 whenever a merge is needed. Similar to what the manifest would do. |
104 existing = old.readlines() |
104 """ |
105 with open(filename, 'wb') as target: |
105 for old, left, right in zip(base, left, right): |
106 for line in filecontent(idx, existing): |
106 if old == left and old == right: |
107 target.write(line) |
107 yield old |
|
108 elif old == left and old != right: |
|
109 yield right |
|
110 elif old != left and old == right: |
|
111 yield left |
|
112 else: |
|
113 yield nextcontent(left + right) |
|
114 |
|
115 |
|
116 def ancestors(graph, rev): |
|
117 """return the set of ancestors of revision <rev>""" |
|
118 to_proceed = {rev} |
|
119 seen = set(to_proceed) |
|
120 while to_proceed: |
|
121 current = to_proceed.pop() |
|
122 for p in graph[current]: |
|
123 if p is None: |
|
124 continue |
|
125 if p in seen: |
|
126 continue |
|
127 to_proceed.add(p) |
|
128 seen.add(p) |
|
129 return seen |
|
130 |
|
131 |
|
132 def gca(graph, left, right): |
|
133 """find the greater common ancestors of left and right |
|
134 |
|
135 Note that the algorithm is stupid and N² when run on all merge, however |
|
136 this should not be a too much issue given the current scale. |
|
137 """ |
|
138 return max(ancestors(graph, left) & ancestors(graph, right)) |
|
139 |
|
140 |
|
141 def make_one_content_fn(idx, base, left, right): |
|
142 """build a function that build the content on demand |
|
143 |
|
144 The dependency are kept are reference to make sure they are not |
|
145 garbage-collected until we use them. Once we computed the current content, |
|
146 we make sure to drop their reference to allow them to be garbage collected. |
|
147 """ |
|
148 |
|
149 def content_fn(idx=idx, base=base, left=left, right=right): |
|
150 if left is None: |
|
151 new = filecontent(idx, None) |
|
152 elif base is None: |
|
153 new = filecontent(idx, left()) |
|
154 else: |
|
155 merged = merge_content(base(), left(), right()) |
|
156 new = filecontent(idx, list(merged)) |
|
157 return list(new) |
|
158 |
|
159 del idx |
|
160 del base |
|
161 del left |
|
162 del right |
|
163 |
|
164 value = None |
|
165 cf = [content_fn] |
|
166 del content_fn |
|
167 |
|
168 def final_fn(): |
|
169 nonlocal value |
|
170 if value is None: |
|
171 content_fn = cf.pop() |
|
172 value = list(content_fn()) |
|
173 del content_fn |
|
174 return value |
|
175 |
|
176 return final_fn |
|
177 |
|
178 |
|
179 def build_content_graph(graph): |
|
180 """produce file content for all revision |
|
181 |
|
182 The content will be generated on demande and cached. Cleanup the |
|
183 dictionnary are you use it to reduce memory usage. |
|
184 """ |
|
185 content = {} |
|
186 for idx, (p1, p2) in graph.items(): |
|
187 base = left = right = None |
|
188 if p1 is not None: |
|
189 left = content[p1] |
|
190 if p2 is not None: |
|
191 right = content[p2] |
|
192 base_rev = gca(graph, p1, p2) |
|
193 base = content[base_rev] |
|
194 content[idx] = make_one_content_fn(idx, base, left, right) |
|
195 return content |
|
196 |
|
197 |
|
198 CONTENT = build_content_graph(GRAPH) |
108 |
199 |
109 |
200 |
110 def hg(command, *args): |
201 def hg(command, *args): |
111 """call a mercurial command with appropriate config and argument""" |
202 """call a mercurial command with appropriate config and argument""" |
112 env = os.environ.copy() |
203 env = os.environ.copy() |
137 print("generating commit #%d/%d" % (idx, NB_CHANGESET)) |
228 print("generating commit #%d/%d" % (idx, NB_CHANGESET)) |
138 if p1 is not None and p1 != idx - 1: |
229 if p1 is not None and p1 != idx - 1: |
139 hg('update', "%d" % p1) |
230 hg('update', "%d" % p1) |
140 if p2 is not None: |
231 if p2 is not None: |
141 hg('merge', "%d" % p2) |
232 hg('merge', "%d" % p2) |
142 updatefile(FILENAME, idx) |
233 with open(FILENAME, 'wb') as f: |
|
234 # pop the value to let it be garbage collection eventually. |
|
235 for line in CONTENT.pop(idx)(): |
|
236 f.write(line) |
143 if idx == 0: |
237 if idx == 0: |
144 hg('add', FILENAME) |
238 hg('add', FILENAME) |
145 hg('commit', '--addremove', '--message', 'initial commit') |
239 hg('commit', '--addremove', '--message', 'initial commit') |
146 else: |
240 else: |
147 hg('commit', '--message', 'commit #%d' % idx) |
241 hg('commit', '--message', 'commit #%d' % idx) |