##// END OF EJS Templates
changing-files: fix docstring...
marmoute -
r46196:646a676f default
parent child Browse files
Show More
@@ -1,557 +1,557
1 # metadata.py -- code related to various metadata computation and access.
1 # metadata.py -- code related to various metadata computation and access.
2 #
2 #
3 # Copyright 2019 Google, Inc <martinvonz@google.com>
3 # Copyright 2019 Google, Inc <martinvonz@google.com>
4 # Copyright 2020 Pierre-Yves David <pierre-yves.david@octobus.net>
4 # Copyright 2020 Pierre-Yves David <pierre-yves.david@octobus.net>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8 from __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import multiprocessing
10 import multiprocessing
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 node,
14 node,
15 pycompat,
15 pycompat,
16 util,
16 util,
17 )
17 )
18
18
19 from .revlogutils import (
19 from .revlogutils import (
20 flagutil as sidedataflag,
20 flagutil as sidedataflag,
21 sidedata as sidedatamod,
21 sidedata as sidedatamod,
22 )
22 )
23
23
24
24
25 class ChangingFiles(object):
25 class ChangingFiles(object):
26 """A class recording the changes made to a file by a changeset
26 """A class recording the changes made to files by a changeset
27
27
28 Actions performed on files are gathered into 3 sets:
28 Actions performed on files are gathered into 3 sets:
29
29
30 - added: files actively added in the changeset.
30 - added: files actively added in the changeset.
31 - merged: files whose history got merged
31 - merged: files whose history got merged
32 - removed: files removed in the revision
32 - removed: files removed in the revision
33 - touched: files affected by the merge
33 - touched: files affected by the merge
34
34
35 and copies information is held by 2 mappings
35 and copies information is held by 2 mappings
36
36
37 - copied_from_p1: {"<new-name>": "<source-name-in-p1>"} mapping for copies
37 - copied_from_p1: {"<new-name>": "<source-name-in-p1>"} mapping for copies
38 - copied_from_p2: {"<new-name>": "<source-name-in-p2>"} mapping for copies
38 - copied_from_p2: {"<new-name>": "<source-name-in-p2>"} mapping for copies
39
39
40 See their inline help for details.
40 See their inline help for details.
41 """
41 """
42
42
43 def __init__(
43 def __init__(
44 self,
44 self,
45 touched=None,
45 touched=None,
46 added=None,
46 added=None,
47 removed=None,
47 removed=None,
48 merged=None,
48 merged=None,
49 p1_copies=None,
49 p1_copies=None,
50 p2_copies=None,
50 p2_copies=None,
51 ):
51 ):
52 self._added = set(() if added is None else added)
52 self._added = set(() if added is None else added)
53 self._merged = set(() if merged is None else merged)
53 self._merged = set(() if merged is None else merged)
54 self._removed = set(() if removed is None else removed)
54 self._removed = set(() if removed is None else removed)
55 self._touched = set(() if touched is None else touched)
55 self._touched = set(() if touched is None else touched)
56 self._touched.update(self._added)
56 self._touched.update(self._added)
57 self._touched.update(self._merged)
57 self._touched.update(self._merged)
58 self._touched.update(self._removed)
58 self._touched.update(self._removed)
59 self._p1_copies = dict(() if p1_copies is None else p1_copies)
59 self._p1_copies = dict(() if p1_copies is None else p1_copies)
60 self._p2_copies = dict(() if p2_copies is None else p2_copies)
60 self._p2_copies = dict(() if p2_copies is None else p2_copies)
61
61
62 def __eq__(self, other):
62 def __eq__(self, other):
63 return (
63 return (
64 self.added == other.added
64 self.added == other.added
65 and self.merged == other.merged
65 and self.merged == other.merged
66 and self.removed == other.removed
66 and self.removed == other.removed
67 and self.touched == other.touched
67 and self.touched == other.touched
68 and self.copied_from_p1 == other.copied_from_p1
68 and self.copied_from_p1 == other.copied_from_p1
69 and self.copied_from_p2 == other.copied_from_p2
69 and self.copied_from_p2 == other.copied_from_p2
70 )
70 )
71
71
72 @property
72 @property
73 def added(self):
73 def added(self):
74 """files actively added in the changeset
74 """files actively added in the changeset
75
75
76 Any file present in that revision that was absent in all the changeset's
76 Any file present in that revision that was absent in all the changeset's
77 parents.
77 parents.
78
78
79 In case of merge, this means a file absent in one of the parents but
79 In case of merge, this means a file absent in one of the parents but
80 existing in the other will *not* be contained in this set. (They were
80 existing in the other will *not* be contained in this set. (They were
81 added by an ancestor)
81 added by an ancestor)
82 """
82 """
83 return frozenset(self._added)
83 return frozenset(self._added)
84
84
85 def mark_added(self, filename):
85 def mark_added(self, filename):
86 self._added.add(filename)
86 self._added.add(filename)
87 self._touched.add(filename)
87 self._touched.add(filename)
88
88
89 def update_added(self, filenames):
89 def update_added(self, filenames):
90 for f in filenames:
90 for f in filenames:
91 self.mark_added(f)
91 self.mark_added(f)
92
92
93 @property
93 @property
94 def merged(self):
94 def merged(self):
95 """files actively merged during a merge
95 """files actively merged during a merge
96
96
97 Any modified files which had modification on both size that needed merging.
97 Any modified files which had modification on both size that needed merging.
98
98
99 In this case a new filenode was created and it has two parents.
99 In this case a new filenode was created and it has two parents.
100 """
100 """
101 return frozenset(self._merged)
101 return frozenset(self._merged)
102
102
103 def mark_merged(self, filename):
103 def mark_merged(self, filename):
104 self._merged.add(filename)
104 self._merged.add(filename)
105 self._touched.add(filename)
105 self._touched.add(filename)
106
106
107 def update_merged(self, filenames):
107 def update_merged(self, filenames):
108 for f in filenames:
108 for f in filenames:
109 self.mark_merged(f)
109 self.mark_merged(f)
110
110
111 @property
111 @property
112 def removed(self):
112 def removed(self):
113 """files actively removed by the changeset
113 """files actively removed by the changeset
114
114
115 In case of merge this will only contain the set of files removing "new"
115 In case of merge this will only contain the set of files removing "new"
116 content. For any file absent in the current changeset:
116 content. For any file absent in the current changeset:
117
117
118 a) If the file exists in both parents, it is clearly "actively" removed
118 a) If the file exists in both parents, it is clearly "actively" removed
119 by this changeset.
119 by this changeset.
120
120
121 b) If a file exists in only one parent and in none of the common
121 b) If a file exists in only one parent and in none of the common
122 ancestors, then the file was newly added in one of the merged branches
122 ancestors, then the file was newly added in one of the merged branches
123 and then got "actively" removed.
123 and then got "actively" removed.
124
124
125 c) If a file exists in only one parent and at least one of the common
125 c) If a file exists in only one parent and at least one of the common
126 ancestors using the same filenode, then the file was unchanged on one
126 ancestors using the same filenode, then the file was unchanged on one
127 side and deleted on the other side. The merge "passively" propagated
127 side and deleted on the other side. The merge "passively" propagated
128 that deletion, but didn't "actively" remove the file. In this case the
128 that deletion, but didn't "actively" remove the file. In this case the
129 file is *not* included in the `removed` set.
129 file is *not* included in the `removed` set.
130
130
131 d) If a file exists in only one parent and at least one of the common
131 d) If a file exists in only one parent and at least one of the common
132 ancestors using a different filenode, then the file was changed on one
132 ancestors using a different filenode, then the file was changed on one
133 side and removed on the other side. The merge process "actively"
133 side and removed on the other side. The merge process "actively"
134 decided to drop the new change and delete the file. Unlike in the
134 decided to drop the new change and delete the file. Unlike in the
135 previous case, (c), the file included in the `removed` set.
135 previous case, (c), the file included in the `removed` set.
136
136
137 Summary table for merge:
137 Summary table for merge:
138
138
139 case | exists in parents | exists in gca || removed
139 case | exists in parents | exists in gca || removed
140 (a) | both | * || yes
140 (a) | both | * || yes
141 (b) | one | none || yes
141 (b) | one | none || yes
142 (c) | one | same filenode || no
142 (c) | one | same filenode || no
143 (d) | one | new filenode || yes
143 (d) | one | new filenode || yes
144 """
144 """
145 return frozenset(self._removed)
145 return frozenset(self._removed)
146
146
147 def mark_removed(self, filename):
147 def mark_removed(self, filename):
148 self._removed.add(filename)
148 self._removed.add(filename)
149 self._touched.add(filename)
149 self._touched.add(filename)
150
150
151 def update_removed(self, filenames):
151 def update_removed(self, filenames):
152 for f in filenames:
152 for f in filenames:
153 self.mark_removed(f)
153 self.mark_removed(f)
154
154
155 @property
155 @property
156 def touched(self):
156 def touched(self):
157 """files either actively modified, added or removed"""
157 """files either actively modified, added or removed"""
158 return frozenset(self._touched)
158 return frozenset(self._touched)
159
159
160 def mark_touched(self, filename):
160 def mark_touched(self, filename):
161 self._touched.add(filename)
161 self._touched.add(filename)
162
162
163 def update_touched(self, filenames):
163 def update_touched(self, filenames):
164 for f in filenames:
164 for f in filenames:
165 self.mark_touched(f)
165 self.mark_touched(f)
166
166
167 @property
167 @property
168 def copied_from_p1(self):
168 def copied_from_p1(self):
169 return self._p1_copies.copy()
169 return self._p1_copies.copy()
170
170
171 def mark_copied_from_p1(self, source, dest):
171 def mark_copied_from_p1(self, source, dest):
172 self._p1_copies[dest] = source
172 self._p1_copies[dest] = source
173
173
174 def update_copies_from_p1(self, copies):
174 def update_copies_from_p1(self, copies):
175 for dest, source in copies.items():
175 for dest, source in copies.items():
176 self.mark_copied_from_p1(source, dest)
176 self.mark_copied_from_p1(source, dest)
177
177
178 @property
178 @property
179 def copied_from_p2(self):
179 def copied_from_p2(self):
180 return self._p2_copies.copy()
180 return self._p2_copies.copy()
181
181
182 def mark_copied_from_p2(self, source, dest):
182 def mark_copied_from_p2(self, source, dest):
183 self._p2_copies[dest] = source
183 self._p2_copies[dest] = source
184
184
185 def update_copies_from_p2(self, copies):
185 def update_copies_from_p2(self, copies):
186 for dest, source in copies.items():
186 for dest, source in copies.items():
187 self.mark_copied_from_p2(source, dest)
187 self.mark_copied_from_p2(source, dest)
188
188
189
189
190 def computechangesetfilesadded(ctx):
190 def computechangesetfilesadded(ctx):
191 """return the list of files added in a changeset
191 """return the list of files added in a changeset
192 """
192 """
193 added = []
193 added = []
194 for f in ctx.files():
194 for f in ctx.files():
195 if not any(f in p for p in ctx.parents()):
195 if not any(f in p for p in ctx.parents()):
196 added.append(f)
196 added.append(f)
197 return added
197 return added
198
198
199
199
200 def get_removal_filter(ctx, x=None):
200 def get_removal_filter(ctx, x=None):
201 """return a function to detect files "wrongly" detected as `removed`
201 """return a function to detect files "wrongly" detected as `removed`
202
202
203 When a file is removed relative to p1 in a merge, this
203 When a file is removed relative to p1 in a merge, this
204 function determines whether the absence is due to a
204 function determines whether the absence is due to a
205 deletion from a parent, or whether the merge commit
205 deletion from a parent, or whether the merge commit
206 itself deletes the file. We decide this by doing a
206 itself deletes the file. We decide this by doing a
207 simplified three way merge of the manifest entry for
207 simplified three way merge of the manifest entry for
208 the file. There are two ways we decide the merge
208 the file. There are two ways we decide the merge
209 itself didn't delete a file:
209 itself didn't delete a file:
210 - neither parent (nor the merge) contain the file
210 - neither parent (nor the merge) contain the file
211 - exactly one parent contains the file, and that
211 - exactly one parent contains the file, and that
212 parent has the same filelog entry as the merge
212 parent has the same filelog entry as the merge
213 ancestor (or all of them if there two). In other
213 ancestor (or all of them if there two). In other
214 words, that parent left the file unchanged while the
214 words, that parent left the file unchanged while the
215 other one deleted it.
215 other one deleted it.
216 One way to think about this is that deleting a file is
216 One way to think about this is that deleting a file is
217 similar to emptying it, so the list of changed files
217 similar to emptying it, so the list of changed files
218 should be similar either way. The computation
218 should be similar either way. The computation
219 described above is not done directly in _filecommit
219 described above is not done directly in _filecommit
220 when creating the list of changed files, however
220 when creating the list of changed files, however
221 it does something very similar by comparing filelog
221 it does something very similar by comparing filelog
222 nodes.
222 nodes.
223 """
223 """
224
224
225 if x is not None:
225 if x is not None:
226 p1, p2, m1, m2 = x
226 p1, p2, m1, m2 = x
227 else:
227 else:
228 p1 = ctx.p1()
228 p1 = ctx.p1()
229 p2 = ctx.p2()
229 p2 = ctx.p2()
230 m1 = p1.manifest()
230 m1 = p1.manifest()
231 m2 = p2.manifest()
231 m2 = p2.manifest()
232
232
233 @util.cachefunc
233 @util.cachefunc
234 def mas():
234 def mas():
235 p1n = p1.node()
235 p1n = p1.node()
236 p2n = p2.node()
236 p2n = p2.node()
237 cahs = ctx.repo().changelog.commonancestorsheads(p1n, p2n)
237 cahs = ctx.repo().changelog.commonancestorsheads(p1n, p2n)
238 if not cahs:
238 if not cahs:
239 cahs = [node.nullrev]
239 cahs = [node.nullrev]
240 return [ctx.repo()[r].manifest() for r in cahs]
240 return [ctx.repo()[r].manifest() for r in cahs]
241
241
242 def deletionfromparent(f):
242 def deletionfromparent(f):
243 if f in m1:
243 if f in m1:
244 return f not in m2 and all(
244 return f not in m2 and all(
245 f in ma and ma.find(f) == m1.find(f) for ma in mas()
245 f in ma and ma.find(f) == m1.find(f) for ma in mas()
246 )
246 )
247 elif f in m2:
247 elif f in m2:
248 return all(f in ma and ma.find(f) == m2.find(f) for ma in mas())
248 return all(f in ma and ma.find(f) == m2.find(f) for ma in mas())
249 else:
249 else:
250 return True
250 return True
251
251
252 return deletionfromparent
252 return deletionfromparent
253
253
254
254
255 def computechangesetfilesremoved(ctx):
255 def computechangesetfilesremoved(ctx):
256 """return the list of files removed in a changeset
256 """return the list of files removed in a changeset
257 """
257 """
258 removed = []
258 removed = []
259 for f in ctx.files():
259 for f in ctx.files():
260 if f not in ctx:
260 if f not in ctx:
261 removed.append(f)
261 removed.append(f)
262 if removed:
262 if removed:
263 rf = get_removal_filter(ctx)
263 rf = get_removal_filter(ctx)
264 removed = [r for r in removed if not rf(r)]
264 removed = [r for r in removed if not rf(r)]
265 return removed
265 return removed
266
266
267
267
268 def computechangesetfilesmerged(ctx):
268 def computechangesetfilesmerged(ctx):
269 """return the list of files merged in a changeset
269 """return the list of files merged in a changeset
270 """
270 """
271 merged = []
271 merged = []
272 if len(ctx.parents()) < 2:
272 if len(ctx.parents()) < 2:
273 return merged
273 return merged
274 for f in ctx.files():
274 for f in ctx.files():
275 if f in ctx:
275 if f in ctx:
276 fctx = ctx[f]
276 fctx = ctx[f]
277 parents = fctx._filelog.parents(fctx._filenode)
277 parents = fctx._filelog.parents(fctx._filenode)
278 if parents[1] != node.nullid:
278 if parents[1] != node.nullid:
279 merged.append(f)
279 merged.append(f)
280 return merged
280 return merged
281
281
282
282
283 def computechangesetcopies(ctx):
283 def computechangesetcopies(ctx):
284 """return the copies data for a changeset
284 """return the copies data for a changeset
285
285
286 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
286 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
287
287
288 Each dictionnary are in the form: `{newname: oldname}`
288 Each dictionnary are in the form: `{newname: oldname}`
289 """
289 """
290 p1copies = {}
290 p1copies = {}
291 p2copies = {}
291 p2copies = {}
292 p1 = ctx.p1()
292 p1 = ctx.p1()
293 p2 = ctx.p2()
293 p2 = ctx.p2()
294 narrowmatch = ctx._repo.narrowmatch()
294 narrowmatch = ctx._repo.narrowmatch()
295 for dst in ctx.files():
295 for dst in ctx.files():
296 if not narrowmatch(dst) or dst not in ctx:
296 if not narrowmatch(dst) or dst not in ctx:
297 continue
297 continue
298 copied = ctx[dst].renamed()
298 copied = ctx[dst].renamed()
299 if not copied:
299 if not copied:
300 continue
300 continue
301 src, srcnode = copied
301 src, srcnode = copied
302 if src in p1 and p1[src].filenode() == srcnode:
302 if src in p1 and p1[src].filenode() == srcnode:
303 p1copies[dst] = src
303 p1copies[dst] = src
304 elif src in p2 and p2[src].filenode() == srcnode:
304 elif src in p2 and p2[src].filenode() == srcnode:
305 p2copies[dst] = src
305 p2copies[dst] = src
306 return p1copies, p2copies
306 return p1copies, p2copies
307
307
308
308
309 def encodecopies(files, copies):
309 def encodecopies(files, copies):
310 items = []
310 items = []
311 for i, dst in enumerate(files):
311 for i, dst in enumerate(files):
312 if dst in copies:
312 if dst in copies:
313 items.append(b'%d\0%s' % (i, copies[dst]))
313 items.append(b'%d\0%s' % (i, copies[dst]))
314 if len(items) != len(copies):
314 if len(items) != len(copies):
315 raise error.ProgrammingError(
315 raise error.ProgrammingError(
316 b'some copy targets missing from file list'
316 b'some copy targets missing from file list'
317 )
317 )
318 return b"\n".join(items)
318 return b"\n".join(items)
319
319
320
320
321 def decodecopies(files, data):
321 def decodecopies(files, data):
322 try:
322 try:
323 copies = {}
323 copies = {}
324 if not data:
324 if not data:
325 return copies
325 return copies
326 for l in data.split(b'\n'):
326 for l in data.split(b'\n'):
327 strindex, src = l.split(b'\0')
327 strindex, src = l.split(b'\0')
328 i = int(strindex)
328 i = int(strindex)
329 dst = files[i]
329 dst = files[i]
330 copies[dst] = src
330 copies[dst] = src
331 return copies
331 return copies
332 except (ValueError, IndexError):
332 except (ValueError, IndexError):
333 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
333 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
334 # used different syntax for the value.
334 # used different syntax for the value.
335 return None
335 return None
336
336
337
337
338 def encodefileindices(files, subset):
338 def encodefileindices(files, subset):
339 subset = set(subset)
339 subset = set(subset)
340 indices = []
340 indices = []
341 for i, f in enumerate(files):
341 for i, f in enumerate(files):
342 if f in subset:
342 if f in subset:
343 indices.append(b'%d' % i)
343 indices.append(b'%d' % i)
344 return b'\n'.join(indices)
344 return b'\n'.join(indices)
345
345
346
346
347 def decodefileindices(files, data):
347 def decodefileindices(files, data):
348 try:
348 try:
349 subset = []
349 subset = []
350 if not data:
350 if not data:
351 return subset
351 return subset
352 for strindex in data.split(b'\n'):
352 for strindex in data.split(b'\n'):
353 i = int(strindex)
353 i = int(strindex)
354 if i < 0 or i >= len(files):
354 if i < 0 or i >= len(files):
355 return None
355 return None
356 subset.append(files[i])
356 subset.append(files[i])
357 return subset
357 return subset
358 except (ValueError, IndexError):
358 except (ValueError, IndexError):
359 # Perhaps someone had chosen the same key name (e.g. "added") and
359 # Perhaps someone had chosen the same key name (e.g. "added") and
360 # used different syntax for the value.
360 # used different syntax for the value.
361 return None
361 return None
362
362
363
363
364 def encode_files_sidedata(files):
364 def encode_files_sidedata(files):
365 sortedfiles = sorted(files.touched)
365 sortedfiles = sorted(files.touched)
366 sidedata = {}
366 sidedata = {}
367 p1copies = files.copied_from_p1
367 p1copies = files.copied_from_p1
368 if p1copies:
368 if p1copies:
369 p1copies = encodecopies(sortedfiles, p1copies)
369 p1copies = encodecopies(sortedfiles, p1copies)
370 sidedata[sidedatamod.SD_P1COPIES] = p1copies
370 sidedata[sidedatamod.SD_P1COPIES] = p1copies
371 p2copies = files.copied_from_p2
371 p2copies = files.copied_from_p2
372 if p2copies:
372 if p2copies:
373 p2copies = encodecopies(sortedfiles, p2copies)
373 p2copies = encodecopies(sortedfiles, p2copies)
374 sidedata[sidedatamod.SD_P2COPIES] = p2copies
374 sidedata[sidedatamod.SD_P2COPIES] = p2copies
375 filesadded = files.added
375 filesadded = files.added
376 if filesadded:
376 if filesadded:
377 filesadded = encodefileindices(sortedfiles, filesadded)
377 filesadded = encodefileindices(sortedfiles, filesadded)
378 sidedata[sidedatamod.SD_FILESADDED] = filesadded
378 sidedata[sidedatamod.SD_FILESADDED] = filesadded
379 filesremoved = files.removed
379 filesremoved = files.removed
380 if filesremoved:
380 if filesremoved:
381 filesremoved = encodefileindices(sortedfiles, filesremoved)
381 filesremoved = encodefileindices(sortedfiles, filesremoved)
382 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
382 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
383 if not sidedata:
383 if not sidedata:
384 sidedata = None
384 sidedata = None
385 return sidedata
385 return sidedata
386
386
387
387
388 def decode_files_sidedata(changelogrevision, sidedata):
388 def decode_files_sidedata(changelogrevision, sidedata):
389 """Return a ChangingFiles instance from a changelogrevision using sidata
389 """Return a ChangingFiles instance from a changelogrevision using sidata
390 """
390 """
391 touched = changelogrevision.files
391 touched = changelogrevision.files
392
392
393 rawindices = sidedata.get(sidedatamod.SD_FILESADDED)
393 rawindices = sidedata.get(sidedatamod.SD_FILESADDED)
394 added = decodefileindices(touched, rawindices)
394 added = decodefileindices(touched, rawindices)
395
395
396 rawindices = sidedata.get(sidedatamod.SD_FILESREMOVED)
396 rawindices = sidedata.get(sidedatamod.SD_FILESREMOVED)
397 removed = decodefileindices(touched, rawindices)
397 removed = decodefileindices(touched, rawindices)
398
398
399 rawcopies = sidedata.get(sidedatamod.SD_P1COPIES)
399 rawcopies = sidedata.get(sidedatamod.SD_P1COPIES)
400 p1_copies = decodecopies(touched, rawcopies)
400 p1_copies = decodecopies(touched, rawcopies)
401
401
402 rawcopies = sidedata.get(sidedatamod.SD_P2COPIES)
402 rawcopies = sidedata.get(sidedatamod.SD_P2COPIES)
403 p2_copies = decodecopies(touched, rawcopies)
403 p2_copies = decodecopies(touched, rawcopies)
404
404
405 return ChangingFiles(
405 return ChangingFiles(
406 touched=touched,
406 touched=touched,
407 added=added,
407 added=added,
408 removed=removed,
408 removed=removed,
409 p1_copies=p1_copies,
409 p1_copies=p1_copies,
410 p2_copies=p2_copies,
410 p2_copies=p2_copies,
411 )
411 )
412
412
413
413
414 def _getsidedata(srcrepo, rev):
414 def _getsidedata(srcrepo, rev):
415 ctx = srcrepo[rev]
415 ctx = srcrepo[rev]
416 filescopies = computechangesetcopies(ctx)
416 filescopies = computechangesetcopies(ctx)
417 filesadded = computechangesetfilesadded(ctx)
417 filesadded = computechangesetfilesadded(ctx)
418 filesremoved = computechangesetfilesremoved(ctx)
418 filesremoved = computechangesetfilesremoved(ctx)
419 sidedata = {}
419 sidedata = {}
420 if any([filescopies, filesadded, filesremoved]):
420 if any([filescopies, filesadded, filesremoved]):
421 sortedfiles = sorted(ctx.files())
421 sortedfiles = sorted(ctx.files())
422 p1copies, p2copies = filescopies
422 p1copies, p2copies = filescopies
423 p1copies = encodecopies(sortedfiles, p1copies)
423 p1copies = encodecopies(sortedfiles, p1copies)
424 p2copies = encodecopies(sortedfiles, p2copies)
424 p2copies = encodecopies(sortedfiles, p2copies)
425 filesadded = encodefileindices(sortedfiles, filesadded)
425 filesadded = encodefileindices(sortedfiles, filesadded)
426 filesremoved = encodefileindices(sortedfiles, filesremoved)
426 filesremoved = encodefileindices(sortedfiles, filesremoved)
427 if p1copies:
427 if p1copies:
428 sidedata[sidedatamod.SD_P1COPIES] = p1copies
428 sidedata[sidedatamod.SD_P1COPIES] = p1copies
429 if p2copies:
429 if p2copies:
430 sidedata[sidedatamod.SD_P2COPIES] = p2copies
430 sidedata[sidedatamod.SD_P2COPIES] = p2copies
431 if filesadded:
431 if filesadded:
432 sidedata[sidedatamod.SD_FILESADDED] = filesadded
432 sidedata[sidedatamod.SD_FILESADDED] = filesadded
433 if filesremoved:
433 if filesremoved:
434 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
434 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
435 return sidedata
435 return sidedata
436
436
437
437
438 def getsidedataadder(srcrepo, destrepo):
438 def getsidedataadder(srcrepo, destrepo):
439 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
439 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
440 if pycompat.iswindows or not use_w:
440 if pycompat.iswindows or not use_w:
441 return _get_simple_sidedata_adder(srcrepo, destrepo)
441 return _get_simple_sidedata_adder(srcrepo, destrepo)
442 else:
442 else:
443 return _get_worker_sidedata_adder(srcrepo, destrepo)
443 return _get_worker_sidedata_adder(srcrepo, destrepo)
444
444
445
445
446 def _sidedata_worker(srcrepo, revs_queue, sidedata_queue, tokens):
446 def _sidedata_worker(srcrepo, revs_queue, sidedata_queue, tokens):
447 """The function used by worker precomputing sidedata
447 """The function used by worker precomputing sidedata
448
448
449 It read an input queue containing revision numbers
449 It read an input queue containing revision numbers
450 It write in an output queue containing (rev, <sidedata-map>)
450 It write in an output queue containing (rev, <sidedata-map>)
451
451
452 The `None` input value is used as a stop signal.
452 The `None` input value is used as a stop signal.
453
453
454 The `tokens` semaphore is user to avoid having too many unprocessed
454 The `tokens` semaphore is user to avoid having too many unprocessed
455 entries. The workers needs to acquire one token before fetching a task.
455 entries. The workers needs to acquire one token before fetching a task.
456 They will be released by the consumer of the produced data.
456 They will be released by the consumer of the produced data.
457 """
457 """
458 tokens.acquire()
458 tokens.acquire()
459 rev = revs_queue.get()
459 rev = revs_queue.get()
460 while rev is not None:
460 while rev is not None:
461 data = _getsidedata(srcrepo, rev)
461 data = _getsidedata(srcrepo, rev)
462 sidedata_queue.put((rev, data))
462 sidedata_queue.put((rev, data))
463 tokens.acquire()
463 tokens.acquire()
464 rev = revs_queue.get()
464 rev = revs_queue.get()
465 # processing of `None` is completed, release the token.
465 # processing of `None` is completed, release the token.
466 tokens.release()
466 tokens.release()
467
467
468
468
469 BUFF_PER_WORKER = 50
469 BUFF_PER_WORKER = 50
470
470
471
471
472 def _get_worker_sidedata_adder(srcrepo, destrepo):
472 def _get_worker_sidedata_adder(srcrepo, destrepo):
473 """The parallel version of the sidedata computation
473 """The parallel version of the sidedata computation
474
474
475 This code spawn a pool of worker that precompute a buffer of sidedata
475 This code spawn a pool of worker that precompute a buffer of sidedata
476 before we actually need them"""
476 before we actually need them"""
477 # avoid circular import copies -> scmutil -> worker -> copies
477 # avoid circular import copies -> scmutil -> worker -> copies
478 from . import worker
478 from . import worker
479
479
480 nbworkers = worker._numworkers(srcrepo.ui)
480 nbworkers = worker._numworkers(srcrepo.ui)
481
481
482 tokens = multiprocessing.BoundedSemaphore(nbworkers * BUFF_PER_WORKER)
482 tokens = multiprocessing.BoundedSemaphore(nbworkers * BUFF_PER_WORKER)
483 revsq = multiprocessing.Queue()
483 revsq = multiprocessing.Queue()
484 sidedataq = multiprocessing.Queue()
484 sidedataq = multiprocessing.Queue()
485
485
486 assert srcrepo.filtername is None
486 assert srcrepo.filtername is None
487 # queue all tasks beforehand, revision numbers are small and it make
487 # queue all tasks beforehand, revision numbers are small and it make
488 # synchronisation simpler
488 # synchronisation simpler
489 #
489 #
490 # Since the computation for each node can be quite expensive, the overhead
490 # Since the computation for each node can be quite expensive, the overhead
491 # of using a single queue is not revelant. In practice, most computation
491 # of using a single queue is not revelant. In practice, most computation
492 # are fast but some are very expensive and dominate all the other smaller
492 # are fast but some are very expensive and dominate all the other smaller
493 # cost.
493 # cost.
494 for r in srcrepo.changelog.revs():
494 for r in srcrepo.changelog.revs():
495 revsq.put(r)
495 revsq.put(r)
496 # queue the "no more tasks" markers
496 # queue the "no more tasks" markers
497 for i in range(nbworkers):
497 for i in range(nbworkers):
498 revsq.put(None)
498 revsq.put(None)
499
499
500 allworkers = []
500 allworkers = []
501 for i in range(nbworkers):
501 for i in range(nbworkers):
502 args = (srcrepo, revsq, sidedataq, tokens)
502 args = (srcrepo, revsq, sidedataq, tokens)
503 w = multiprocessing.Process(target=_sidedata_worker, args=args)
503 w = multiprocessing.Process(target=_sidedata_worker, args=args)
504 allworkers.append(w)
504 allworkers.append(w)
505 w.start()
505 w.start()
506
506
507 # dictionnary to store results for revision higher than we one we are
507 # dictionnary to store results for revision higher than we one we are
508 # looking for. For example, if we need the sidedatamap for 42, and 43 is
508 # looking for. For example, if we need the sidedatamap for 42, and 43 is
509 # received, when shelve 43 for later use.
509 # received, when shelve 43 for later use.
510 staging = {}
510 staging = {}
511
511
512 def sidedata_companion(revlog, rev):
512 def sidedata_companion(revlog, rev):
513 sidedata = {}
513 sidedata = {}
514 if util.safehasattr(revlog, b'filteredrevs'): # this is a changelog
514 if util.safehasattr(revlog, b'filteredrevs'): # this is a changelog
515 # Is the data previously shelved ?
515 # Is the data previously shelved ?
516 sidedata = staging.pop(rev, None)
516 sidedata = staging.pop(rev, None)
517 if sidedata is None:
517 if sidedata is None:
518 # look at the queued result until we find the one we are lookig
518 # look at the queued result until we find the one we are lookig
519 # for (shelve the other ones)
519 # for (shelve the other ones)
520 r, sidedata = sidedataq.get()
520 r, sidedata = sidedataq.get()
521 while r != rev:
521 while r != rev:
522 staging[r] = sidedata
522 staging[r] = sidedata
523 r, sidedata = sidedataq.get()
523 r, sidedata = sidedataq.get()
524 tokens.release()
524 tokens.release()
525 return False, (), sidedata
525 return False, (), sidedata
526
526
527 return sidedata_companion
527 return sidedata_companion
528
528
529
529
530 def _get_simple_sidedata_adder(srcrepo, destrepo):
530 def _get_simple_sidedata_adder(srcrepo, destrepo):
531 """The simple version of the sidedata computation
531 """The simple version of the sidedata computation
532
532
533 It just compute it in the same thread on request"""
533 It just compute it in the same thread on request"""
534
534
535 def sidedatacompanion(revlog, rev):
535 def sidedatacompanion(revlog, rev):
536 sidedata = {}
536 sidedata = {}
537 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
537 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
538 sidedata = _getsidedata(srcrepo, rev)
538 sidedata = _getsidedata(srcrepo, rev)
539 return False, (), sidedata
539 return False, (), sidedata
540
540
541 return sidedatacompanion
541 return sidedatacompanion
542
542
543
543
544 def getsidedataremover(srcrepo, destrepo):
544 def getsidedataremover(srcrepo, destrepo):
545 def sidedatacompanion(revlog, rev):
545 def sidedatacompanion(revlog, rev):
546 f = ()
546 f = ()
547 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
547 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
548 if revlog.flags(rev) & sidedataflag.REVIDX_SIDEDATA:
548 if revlog.flags(rev) & sidedataflag.REVIDX_SIDEDATA:
549 f = (
549 f = (
550 sidedatamod.SD_P1COPIES,
550 sidedatamod.SD_P1COPIES,
551 sidedatamod.SD_P2COPIES,
551 sidedatamod.SD_P2COPIES,
552 sidedatamod.SD_FILESADDED,
552 sidedatamod.SD_FILESADDED,
553 sidedatamod.SD_FILESREMOVED,
553 sidedatamod.SD_FILESREMOVED,
554 )
554 )
555 return False, f, {}
555 return False, f, {}
556
556
557 return sidedatacompanion
557 return sidedatacompanion
General Comments 0
You need to be logged in to leave comments. Login now