##// END OF EJS Templates
branchmap: correctly set()-ify list argument...
Augie Fackler -
r44033:43f57b96 default
parent child Browse files
Show More
@@ -1,738 +1,738 b''
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import struct
10 import struct
11
11
12 from .node import (
12 from .node import (
13 bin,
13 bin,
14 hex,
14 hex,
15 nullid,
15 nullid,
16 nullrev,
16 nullrev,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 pycompat,
21 pycompat,
22 scmutil,
22 scmutil,
23 util,
23 util,
24 )
24 )
25 from .utils import (
25 from .utils import (
26 repoviewutil,
26 repoviewutil,
27 stringutil,
27 stringutil,
28 )
28 )
29
29
30 subsettable = repoviewutil.subsettable
30 subsettable = repoviewutil.subsettable
31
31
32 calcsize = struct.calcsize
32 calcsize = struct.calcsize
33 pack_into = struct.pack_into
33 pack_into = struct.pack_into
34 unpack_from = struct.unpack_from
34 unpack_from = struct.unpack_from
35
35
36
36
37 class BranchMapCache(object):
37 class BranchMapCache(object):
38 """mapping of filtered views of repo with their branchcache"""
38 """mapping of filtered views of repo with their branchcache"""
39
39
40 def __init__(self):
40 def __init__(self):
41 self._per_filter = {}
41 self._per_filter = {}
42
42
43 def __getitem__(self, repo):
43 def __getitem__(self, repo):
44 self.updatecache(repo)
44 self.updatecache(repo)
45 return self._per_filter[repo.filtername]
45 return self._per_filter[repo.filtername]
46
46
47 def updatecache(self, repo):
47 def updatecache(self, repo):
48 """Update the cache for the given filtered view on a repository"""
48 """Update the cache for the given filtered view on a repository"""
49 # This can trigger updates for the caches for subsets of the filtered
49 # This can trigger updates for the caches for subsets of the filtered
50 # view, e.g. when there is no cache for this filtered view or the cache
50 # view, e.g. when there is no cache for this filtered view or the cache
51 # is stale.
51 # is stale.
52
52
53 cl = repo.changelog
53 cl = repo.changelog
54 filtername = repo.filtername
54 filtername = repo.filtername
55 bcache = self._per_filter.get(filtername)
55 bcache = self._per_filter.get(filtername)
56 if bcache is None or not bcache.validfor(repo):
56 if bcache is None or not bcache.validfor(repo):
57 # cache object missing or cache object stale? Read from disk
57 # cache object missing or cache object stale? Read from disk
58 bcache = branchcache.fromfile(repo)
58 bcache = branchcache.fromfile(repo)
59
59
60 revs = []
60 revs = []
61 if bcache is None:
61 if bcache is None:
62 # no (fresh) cache available anymore, perhaps we can re-use
62 # no (fresh) cache available anymore, perhaps we can re-use
63 # the cache for a subset, then extend that to add info on missing
63 # the cache for a subset, then extend that to add info on missing
64 # revisions.
64 # revisions.
65 subsetname = subsettable.get(filtername)
65 subsetname = subsettable.get(filtername)
66 if subsetname is not None:
66 if subsetname is not None:
67 subset = repo.filtered(subsetname)
67 subset = repo.filtered(subsetname)
68 bcache = self[subset].copy()
68 bcache = self[subset].copy()
69 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
69 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
70 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
70 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
71 else:
71 else:
72 # nothing to fall back on, start empty.
72 # nothing to fall back on, start empty.
73 bcache = branchcache()
73 bcache = branchcache()
74
74
75 revs.extend(cl.revs(start=bcache.tiprev + 1))
75 revs.extend(cl.revs(start=bcache.tiprev + 1))
76 if revs:
76 if revs:
77 bcache.update(repo, revs)
77 bcache.update(repo, revs)
78
78
79 assert bcache.validfor(repo), filtername
79 assert bcache.validfor(repo), filtername
80 self._per_filter[repo.filtername] = bcache
80 self._per_filter[repo.filtername] = bcache
81
81
82 def replace(self, repo, remotebranchmap):
82 def replace(self, repo, remotebranchmap):
83 """Replace the branchmap cache for a repo with a branch mapping.
83 """Replace the branchmap cache for a repo with a branch mapping.
84
84
85 This is likely only called during clone with a branch map from a
85 This is likely only called during clone with a branch map from a
86 remote.
86 remote.
87
87
88 """
88 """
89 cl = repo.changelog
89 cl = repo.changelog
90 clrev = cl.rev
90 clrev = cl.rev
91 clbranchinfo = cl.branchinfo
91 clbranchinfo = cl.branchinfo
92 rbheads = []
92 rbheads = []
93 closed = []
93 closed = []
94 for bheads in pycompat.itervalues(remotebranchmap):
94 for bheads in pycompat.itervalues(remotebranchmap):
95 rbheads += bheads
95 rbheads += bheads
96 for h in bheads:
96 for h in bheads:
97 r = clrev(h)
97 r = clrev(h)
98 b, c = clbranchinfo(r)
98 b, c = clbranchinfo(r)
99 if c:
99 if c:
100 closed.append(h)
100 closed.append(h)
101
101
102 if rbheads:
102 if rbheads:
103 rtiprev = max((int(clrev(node)) for node in rbheads))
103 rtiprev = max((int(clrev(node)) for node in rbheads))
104 cache = branchcache(
104 cache = branchcache(
105 remotebranchmap,
105 remotebranchmap,
106 repo[rtiprev].node(),
106 repo[rtiprev].node(),
107 rtiprev,
107 rtiprev,
108 closednodes=closed,
108 closednodes=set(closed),
109 )
109 )
110
110
111 # Try to stick it as low as possible
111 # Try to stick it as low as possible
112 # filter above served are unlikely to be fetch from a clone
112 # filter above served are unlikely to be fetch from a clone
113 for candidate in (b'base', b'immutable', b'served'):
113 for candidate in (b'base', b'immutable', b'served'):
114 rview = repo.filtered(candidate)
114 rview = repo.filtered(candidate)
115 if cache.validfor(rview):
115 if cache.validfor(rview):
116 self._per_filter[candidate] = cache
116 self._per_filter[candidate] = cache
117 cache.write(rview)
117 cache.write(rview)
118 return
118 return
119
119
120 def clear(self):
120 def clear(self):
121 self._per_filter.clear()
121 self._per_filter.clear()
122
122
123
123
124 def _unknownnode(node):
124 def _unknownnode(node):
125 """ raises ValueError when branchcache found a node which does not exists
125 """ raises ValueError when branchcache found a node which does not exists
126 """
126 """
127 raise ValueError('node %s does not exist' % pycompat.sysstr(hex(node)))
127 raise ValueError('node %s does not exist' % pycompat.sysstr(hex(node)))
128
128
129
129
130 def _branchcachedesc(repo):
130 def _branchcachedesc(repo):
131 if repo.filtername is not None:
131 if repo.filtername is not None:
132 return b'branch cache (%s)' % repo.filtername
132 return b'branch cache (%s)' % repo.filtername
133 else:
133 else:
134 return b'branch cache'
134 return b'branch cache'
135
135
136
136
137 class branchcache(object):
137 class branchcache(object):
138 """A dict like object that hold branches heads cache.
138 """A dict like object that hold branches heads cache.
139
139
140 This cache is used to avoid costly computations to determine all the
140 This cache is used to avoid costly computations to determine all the
141 branch heads of a repo.
141 branch heads of a repo.
142
142
143 The cache is serialized on disk in the following format:
143 The cache is serialized on disk in the following format:
144
144
145 <tip hex node> <tip rev number> [optional filtered repo hex hash]
145 <tip hex node> <tip rev number> [optional filtered repo hex hash]
146 <branch head hex node> <open/closed state> <branch name>
146 <branch head hex node> <open/closed state> <branch name>
147 <branch head hex node> <open/closed state> <branch name>
147 <branch head hex node> <open/closed state> <branch name>
148 ...
148 ...
149
149
150 The first line is used to check if the cache is still valid. If the
150 The first line is used to check if the cache is still valid. If the
151 branch cache is for a filtered repo view, an optional third hash is
151 branch cache is for a filtered repo view, an optional third hash is
152 included that hashes the hashes of all filtered revisions.
152 included that hashes the hashes of all filtered revisions.
153
153
154 The open/closed state is represented by a single letter 'o' or 'c'.
154 The open/closed state is represented by a single letter 'o' or 'c'.
155 This field can be used to avoid changelog reads when determining if a
155 This field can be used to avoid changelog reads when determining if a
156 branch head closes a branch or not.
156 branch head closes a branch or not.
157 """
157 """
158
158
159 def __init__(
159 def __init__(
160 self,
160 self,
161 entries=(),
161 entries=(),
162 tipnode=nullid,
162 tipnode=nullid,
163 tiprev=nullrev,
163 tiprev=nullrev,
164 filteredhash=None,
164 filteredhash=None,
165 closednodes=None,
165 closednodes=None,
166 hasnode=None,
166 hasnode=None,
167 ):
167 ):
168 """ hasnode is a function which can be used to verify whether changelog
168 """ hasnode is a function which can be used to verify whether changelog
169 has a given node or not. If it's not provided, we assume that every node
169 has a given node or not. If it's not provided, we assume that every node
170 we have exists in changelog """
170 we have exists in changelog """
171 self.tipnode = tipnode
171 self.tipnode = tipnode
172 self.tiprev = tiprev
172 self.tiprev = tiprev
173 self.filteredhash = filteredhash
173 self.filteredhash = filteredhash
174 # closednodes is a set of nodes that close their branch. If the branch
174 # closednodes is a set of nodes that close their branch. If the branch
175 # cache has been updated, it may contain nodes that are no longer
175 # cache has been updated, it may contain nodes that are no longer
176 # heads.
176 # heads.
177 if closednodes is None:
177 if closednodes is None:
178 self._closednodes = set()
178 self._closednodes = set()
179 else:
179 else:
180 self._closednodes = closednodes
180 self._closednodes = set(closednodes)
181 self._entries = dict(entries)
181 self._entries = dict(entries)
182 # whether closed nodes are verified or not
182 # whether closed nodes are verified or not
183 self._closedverified = False
183 self._closedverified = False
184 # branches for which nodes are verified
184 # branches for which nodes are verified
185 self._verifiedbranches = set()
185 self._verifiedbranches = set()
186 self._hasnode = hasnode
186 self._hasnode = hasnode
187 if self._hasnode is None:
187 if self._hasnode is None:
188 self._hasnode = lambda x: True
188 self._hasnode = lambda x: True
189
189
190 def _verifyclosed(self):
190 def _verifyclosed(self):
191 """ verify the closed nodes we have """
191 """ verify the closed nodes we have """
192 if self._closedverified:
192 if self._closedverified:
193 return
193 return
194 for node in self._closednodes:
194 for node in self._closednodes:
195 if not self._hasnode(node):
195 if not self._hasnode(node):
196 _unknownnode(node)
196 _unknownnode(node)
197
197
198 self._closedverified = True
198 self._closedverified = True
199
199
200 def _verifybranch(self, branch):
200 def _verifybranch(self, branch):
201 """ verify head nodes for the given branch. """
201 """ verify head nodes for the given branch. """
202 if branch not in self._entries or branch in self._verifiedbranches:
202 if branch not in self._entries or branch in self._verifiedbranches:
203 return
203 return
204 for n in self._entries[branch]:
204 for n in self._entries[branch]:
205 if not self._hasnode(n):
205 if not self._hasnode(n):
206 _unknownnode(n)
206 _unknownnode(n)
207
207
208 self._verifiedbranches.add(branch)
208 self._verifiedbranches.add(branch)
209
209
210 def _verifyall(self):
210 def _verifyall(self):
211 """ verifies nodes of all the branches """
211 """ verifies nodes of all the branches """
212 needverification = set(self._entries.keys()) - self._verifiedbranches
212 needverification = set(self._entries.keys()) - self._verifiedbranches
213 for b in needverification:
213 for b in needverification:
214 self._verifybranch(b)
214 self._verifybranch(b)
215
215
216 def __iter__(self):
216 def __iter__(self):
217 return iter(self._entries)
217 return iter(self._entries)
218
218
219 def __setitem__(self, key, value):
219 def __setitem__(self, key, value):
220 self._entries[key] = value
220 self._entries[key] = value
221
221
222 def __getitem__(self, key):
222 def __getitem__(self, key):
223 self._verifybranch(key)
223 self._verifybranch(key)
224 return self._entries[key]
224 return self._entries[key]
225
225
226 def __contains__(self, key):
226 def __contains__(self, key):
227 self._verifybranch(key)
227 self._verifybranch(key)
228 return key in self._entries
228 return key in self._entries
229
229
230 def iteritems(self):
230 def iteritems(self):
231 for k, v in pycompat.iteritems(self._entries):
231 for k, v in pycompat.iteritems(self._entries):
232 self._verifybranch(k)
232 self._verifybranch(k)
233 yield k, v
233 yield k, v
234
234
235 items = iteritems
235 items = iteritems
236
236
237 def hasbranch(self, label):
237 def hasbranch(self, label):
238 """ checks whether a branch of this name exists or not """
238 """ checks whether a branch of this name exists or not """
239 self._verifybranch(label)
239 self._verifybranch(label)
240 return label in self._entries
240 return label in self._entries
241
241
242 @classmethod
242 @classmethod
243 def fromfile(cls, repo):
243 def fromfile(cls, repo):
244 f = None
244 f = None
245 try:
245 try:
246 f = repo.cachevfs(cls._filename(repo))
246 f = repo.cachevfs(cls._filename(repo))
247 lineiter = iter(f)
247 lineiter = iter(f)
248 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2)
248 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2)
249 last, lrev = cachekey[:2]
249 last, lrev = cachekey[:2]
250 last, lrev = bin(last), int(lrev)
250 last, lrev = bin(last), int(lrev)
251 filteredhash = None
251 filteredhash = None
252 hasnode = repo.changelog.hasnode
252 hasnode = repo.changelog.hasnode
253 if len(cachekey) > 2:
253 if len(cachekey) > 2:
254 filteredhash = bin(cachekey[2])
254 filteredhash = bin(cachekey[2])
255 bcache = cls(
255 bcache = cls(
256 tipnode=last,
256 tipnode=last,
257 tiprev=lrev,
257 tiprev=lrev,
258 filteredhash=filteredhash,
258 filteredhash=filteredhash,
259 hasnode=hasnode,
259 hasnode=hasnode,
260 )
260 )
261 if not bcache.validfor(repo):
261 if not bcache.validfor(repo):
262 # invalidate the cache
262 # invalidate the cache
263 raise ValueError('tip differs')
263 raise ValueError('tip differs')
264 bcache.load(repo, lineiter)
264 bcache.load(repo, lineiter)
265 except (IOError, OSError):
265 except (IOError, OSError):
266 return None
266 return None
267
267
268 except Exception as inst:
268 except Exception as inst:
269 if repo.ui.debugflag:
269 if repo.ui.debugflag:
270 msg = b'invalid %s: %s\n'
270 msg = b'invalid %s: %s\n'
271 repo.ui.debug(
271 repo.ui.debug(
272 msg
272 msg
273 % (
273 % (
274 _branchcachedesc(repo),
274 _branchcachedesc(repo),
275 pycompat.bytestr(
275 pycompat.bytestr(
276 inst # pytype: disable=wrong-arg-types
276 inst # pytype: disable=wrong-arg-types
277 ),
277 ),
278 )
278 )
279 )
279 )
280 bcache = None
280 bcache = None
281
281
282 finally:
282 finally:
283 if f:
283 if f:
284 f.close()
284 f.close()
285
285
286 return bcache
286 return bcache
287
287
288 def load(self, repo, lineiter):
288 def load(self, repo, lineiter):
289 """ fully loads the branchcache by reading from the file using the line
289 """ fully loads the branchcache by reading from the file using the line
290 iterator passed"""
290 iterator passed"""
291 for line in lineiter:
291 for line in lineiter:
292 line = line.rstrip(b'\n')
292 line = line.rstrip(b'\n')
293 if not line:
293 if not line:
294 continue
294 continue
295 node, state, label = line.split(b" ", 2)
295 node, state, label = line.split(b" ", 2)
296 if state not in b'oc':
296 if state not in b'oc':
297 raise ValueError('invalid branch state')
297 raise ValueError('invalid branch state')
298 label = encoding.tolocal(label.strip())
298 label = encoding.tolocal(label.strip())
299 node = bin(node)
299 node = bin(node)
300 self._entries.setdefault(label, []).append(node)
300 self._entries.setdefault(label, []).append(node)
301 if state == b'c':
301 if state == b'c':
302 self._closednodes.add(node)
302 self._closednodes.add(node)
303
303
304 @staticmethod
304 @staticmethod
305 def _filename(repo):
305 def _filename(repo):
306 """name of a branchcache file for a given repo or repoview"""
306 """name of a branchcache file for a given repo or repoview"""
307 filename = b"branch2"
307 filename = b"branch2"
308 if repo.filtername:
308 if repo.filtername:
309 filename = b'%s-%s' % (filename, repo.filtername)
309 filename = b'%s-%s' % (filename, repo.filtername)
310 return filename
310 return filename
311
311
312 def validfor(self, repo):
312 def validfor(self, repo):
313 """Is the cache content valid regarding a repo
313 """Is the cache content valid regarding a repo
314
314
315 - False when cached tipnode is unknown or if we detect a strip.
315 - False when cached tipnode is unknown or if we detect a strip.
316 - True when cache is up to date or a subset of current repo."""
316 - True when cache is up to date or a subset of current repo."""
317 try:
317 try:
318 return (self.tipnode == repo.changelog.node(self.tiprev)) and (
318 return (self.tipnode == repo.changelog.node(self.tiprev)) and (
319 self.filteredhash == scmutil.filteredhash(repo, self.tiprev)
319 self.filteredhash == scmutil.filteredhash(repo, self.tiprev)
320 )
320 )
321 except IndexError:
321 except IndexError:
322 return False
322 return False
323
323
324 def _branchtip(self, heads):
324 def _branchtip(self, heads):
325 '''Return tuple with last open head in heads and false,
325 '''Return tuple with last open head in heads and false,
326 otherwise return last closed head and true.'''
326 otherwise return last closed head and true.'''
327 tip = heads[-1]
327 tip = heads[-1]
328 closed = True
328 closed = True
329 for h in reversed(heads):
329 for h in reversed(heads):
330 if h not in self._closednodes:
330 if h not in self._closednodes:
331 tip = h
331 tip = h
332 closed = False
332 closed = False
333 break
333 break
334 return tip, closed
334 return tip, closed
335
335
336 def branchtip(self, branch):
336 def branchtip(self, branch):
337 '''Return the tipmost open head on branch head, otherwise return the
337 '''Return the tipmost open head on branch head, otherwise return the
338 tipmost closed head on branch.
338 tipmost closed head on branch.
339 Raise KeyError for unknown branch.'''
339 Raise KeyError for unknown branch.'''
340 return self._branchtip(self[branch])[0]
340 return self._branchtip(self[branch])[0]
341
341
342 def iteropen(self, nodes):
342 def iteropen(self, nodes):
343 return (n for n in nodes if n not in self._closednodes)
343 return (n for n in nodes if n not in self._closednodes)
344
344
345 def branchheads(self, branch, closed=False):
345 def branchheads(self, branch, closed=False):
346 self._verifybranch(branch)
346 self._verifybranch(branch)
347 heads = self._entries[branch]
347 heads = self._entries[branch]
348 if not closed:
348 if not closed:
349 heads = list(self.iteropen(heads))
349 heads = list(self.iteropen(heads))
350 return heads
350 return heads
351
351
352 def iterbranches(self):
352 def iterbranches(self):
353 for bn, heads in pycompat.iteritems(self):
353 for bn, heads in pycompat.iteritems(self):
354 yield (bn, heads) + self._branchtip(heads)
354 yield (bn, heads) + self._branchtip(heads)
355
355
356 def iterheads(self):
356 def iterheads(self):
357 """ returns all the heads """
357 """ returns all the heads """
358 self._verifyall()
358 self._verifyall()
359 return pycompat.itervalues(self._entries)
359 return pycompat.itervalues(self._entries)
360
360
361 def copy(self):
361 def copy(self):
362 """return an deep copy of the branchcache object"""
362 """return an deep copy of the branchcache object"""
363 return type(self)(
363 return type(self)(
364 self._entries,
364 self._entries,
365 self.tipnode,
365 self.tipnode,
366 self.tiprev,
366 self.tiprev,
367 self.filteredhash,
367 self.filteredhash,
368 self._closednodes,
368 self._closednodes,
369 )
369 )
370
370
371 def write(self, repo):
371 def write(self, repo):
372 try:
372 try:
373 f = repo.cachevfs(self._filename(repo), b"w", atomictemp=True)
373 f = repo.cachevfs(self._filename(repo), b"w", atomictemp=True)
374 cachekey = [hex(self.tipnode), b'%d' % self.tiprev]
374 cachekey = [hex(self.tipnode), b'%d' % self.tiprev]
375 if self.filteredhash is not None:
375 if self.filteredhash is not None:
376 cachekey.append(hex(self.filteredhash))
376 cachekey.append(hex(self.filteredhash))
377 f.write(b" ".join(cachekey) + b'\n')
377 f.write(b" ".join(cachekey) + b'\n')
378 nodecount = 0
378 nodecount = 0
379 for label, nodes in sorted(pycompat.iteritems(self._entries)):
379 for label, nodes in sorted(pycompat.iteritems(self._entries)):
380 label = encoding.fromlocal(label)
380 label = encoding.fromlocal(label)
381 for node in nodes:
381 for node in nodes:
382 nodecount += 1
382 nodecount += 1
383 if node in self._closednodes:
383 if node in self._closednodes:
384 state = b'c'
384 state = b'c'
385 else:
385 else:
386 state = b'o'
386 state = b'o'
387 f.write(b"%s %s %s\n" % (hex(node), state, label))
387 f.write(b"%s %s %s\n" % (hex(node), state, label))
388 f.close()
388 f.close()
389 repo.ui.log(
389 repo.ui.log(
390 b'branchcache',
390 b'branchcache',
391 b'wrote %s with %d labels and %d nodes\n',
391 b'wrote %s with %d labels and %d nodes\n',
392 _branchcachedesc(repo),
392 _branchcachedesc(repo),
393 len(self._entries),
393 len(self._entries),
394 nodecount,
394 nodecount,
395 )
395 )
396 except (IOError, OSError, error.Abort) as inst:
396 except (IOError, OSError, error.Abort) as inst:
397 # Abort may be raised by read only opener, so log and continue
397 # Abort may be raised by read only opener, so log and continue
398 repo.ui.debug(
398 repo.ui.debug(
399 b"couldn't write branch cache: %s\n"
399 b"couldn't write branch cache: %s\n"
400 % stringutil.forcebytestr(inst)
400 % stringutil.forcebytestr(inst)
401 )
401 )
402
402
403 def update(self, repo, revgen):
403 def update(self, repo, revgen):
404 """Given a branchhead cache, self, that may have extra nodes or be
404 """Given a branchhead cache, self, that may have extra nodes or be
405 missing heads, and a generator of nodes that are strictly a superset of
405 missing heads, and a generator of nodes that are strictly a superset of
406 heads missing, this function updates self to be correct.
406 heads missing, this function updates self to be correct.
407 """
407 """
408 starttime = util.timer()
408 starttime = util.timer()
409 cl = repo.changelog
409 cl = repo.changelog
410 # collect new branch entries
410 # collect new branch entries
411 newbranches = {}
411 newbranches = {}
412 getbranchinfo = repo.revbranchcache().branchinfo
412 getbranchinfo = repo.revbranchcache().branchinfo
413 for r in revgen:
413 for r in revgen:
414 branch, closesbranch = getbranchinfo(r)
414 branch, closesbranch = getbranchinfo(r)
415 newbranches.setdefault(branch, []).append(r)
415 newbranches.setdefault(branch, []).append(r)
416 if closesbranch:
416 if closesbranch:
417 self._closednodes.add(cl.node(r))
417 self._closednodes.add(cl.node(r))
418
418
419 # fetch current topological heads to speed up filtering
419 # fetch current topological heads to speed up filtering
420 topoheads = set(cl.headrevs())
420 topoheads = set(cl.headrevs())
421
421
422 # new tip revision which we found after iterating items from new
422 # new tip revision which we found after iterating items from new
423 # branches
423 # branches
424 ntiprev = self.tiprev
424 ntiprev = self.tiprev
425
425
426 # if older branchheads are reachable from new ones, they aren't
426 # if older branchheads are reachable from new ones, they aren't
427 # really branchheads. Note checking parents is insufficient:
427 # really branchheads. Note checking parents is insufficient:
428 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
428 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
429 for branch, newheadrevs in pycompat.iteritems(newbranches):
429 for branch, newheadrevs in pycompat.iteritems(newbranches):
430 bheads = self._entries.setdefault(branch, [])
430 bheads = self._entries.setdefault(branch, [])
431 bheadset = set(cl.rev(node) for node in bheads)
431 bheadset = set(cl.rev(node) for node in bheads)
432
432
433 # This have been tested True on all internal usage of this function.
433 # This have been tested True on all internal usage of this function.
434 # run it again in case of doubt
434 # run it again in case of doubt
435 # assert not (set(bheadrevs) & set(newheadrevs))
435 # assert not (set(bheadrevs) & set(newheadrevs))
436 bheadset.update(newheadrevs)
436 bheadset.update(newheadrevs)
437
437
438 # This prunes out two kinds of heads - heads that are superseded by
438 # This prunes out two kinds of heads - heads that are superseded by
439 # a head in newheadrevs, and newheadrevs that are not heads because
439 # a head in newheadrevs, and newheadrevs that are not heads because
440 # an existing head is their descendant.
440 # an existing head is their descendant.
441 uncertain = bheadset - topoheads
441 uncertain = bheadset - topoheads
442 if uncertain:
442 if uncertain:
443 floorrev = min(uncertain)
443 floorrev = min(uncertain)
444 ancestors = set(cl.ancestors(newheadrevs, floorrev))
444 ancestors = set(cl.ancestors(newheadrevs, floorrev))
445 bheadset -= ancestors
445 bheadset -= ancestors
446 bheadrevs = sorted(bheadset)
446 bheadrevs = sorted(bheadset)
447 self[branch] = [cl.node(rev) for rev in bheadrevs]
447 self[branch] = [cl.node(rev) for rev in bheadrevs]
448 tiprev = bheadrevs[-1]
448 tiprev = bheadrevs[-1]
449 if tiprev > ntiprev:
449 if tiprev > ntiprev:
450 ntiprev = tiprev
450 ntiprev = tiprev
451
451
452 if ntiprev > self.tiprev:
452 if ntiprev > self.tiprev:
453 self.tiprev = ntiprev
453 self.tiprev = ntiprev
454 self.tipnode = cl.node(ntiprev)
454 self.tipnode = cl.node(ntiprev)
455
455
456 if not self.validfor(repo):
456 if not self.validfor(repo):
457 # cache key are not valid anymore
457 # cache key are not valid anymore
458 self.tipnode = nullid
458 self.tipnode = nullid
459 self.tiprev = nullrev
459 self.tiprev = nullrev
460 for heads in self.iterheads():
460 for heads in self.iterheads():
461 tiprev = max(cl.rev(node) for node in heads)
461 tiprev = max(cl.rev(node) for node in heads)
462 if tiprev > self.tiprev:
462 if tiprev > self.tiprev:
463 self.tipnode = cl.node(tiprev)
463 self.tipnode = cl.node(tiprev)
464 self.tiprev = tiprev
464 self.tiprev = tiprev
465 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
465 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
466
466
467 duration = util.timer() - starttime
467 duration = util.timer() - starttime
468 repo.ui.log(
468 repo.ui.log(
469 b'branchcache',
469 b'branchcache',
470 b'updated %s in %.4f seconds\n',
470 b'updated %s in %.4f seconds\n',
471 _branchcachedesc(repo),
471 _branchcachedesc(repo),
472 duration,
472 duration,
473 )
473 )
474
474
475 self.write(repo)
475 self.write(repo)
476
476
477
477
478 class remotebranchcache(branchcache):
478 class remotebranchcache(branchcache):
479 """Branchmap info for a remote connection, should not write locally"""
479 """Branchmap info for a remote connection, should not write locally"""
480
480
481 def write(self, repo):
481 def write(self, repo):
482 pass
482 pass
483
483
484
484
485 # Revision branch info cache
485 # Revision branch info cache
486
486
487 _rbcversion = b'-v1'
487 _rbcversion = b'-v1'
488 _rbcnames = b'rbc-names' + _rbcversion
488 _rbcnames = b'rbc-names' + _rbcversion
489 _rbcrevs = b'rbc-revs' + _rbcversion
489 _rbcrevs = b'rbc-revs' + _rbcversion
490 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
490 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
491 _rbcrecfmt = b'>4sI'
491 _rbcrecfmt = b'>4sI'
492 _rbcrecsize = calcsize(_rbcrecfmt)
492 _rbcrecsize = calcsize(_rbcrecfmt)
493 _rbcnodelen = 4
493 _rbcnodelen = 4
494 _rbcbranchidxmask = 0x7FFFFFFF
494 _rbcbranchidxmask = 0x7FFFFFFF
495 _rbccloseflag = 0x80000000
495 _rbccloseflag = 0x80000000
496
496
497
497
498 class revbranchcache(object):
498 class revbranchcache(object):
499 """Persistent cache, mapping from revision number to branch name and close.
499 """Persistent cache, mapping from revision number to branch name and close.
500 This is a low level cache, independent of filtering.
500 This is a low level cache, independent of filtering.
501
501
502 Branch names are stored in rbc-names in internal encoding separated by 0.
502 Branch names are stored in rbc-names in internal encoding separated by 0.
503 rbc-names is append-only, and each branch name is only stored once and will
503 rbc-names is append-only, and each branch name is only stored once and will
504 thus have a unique index.
504 thus have a unique index.
505
505
506 The branch info for each revision is stored in rbc-revs as constant size
506 The branch info for each revision is stored in rbc-revs as constant size
507 records. The whole file is read into memory, but it is only 'parsed' on
507 records. The whole file is read into memory, but it is only 'parsed' on
508 demand. The file is usually append-only but will be truncated if repo
508 demand. The file is usually append-only but will be truncated if repo
509 modification is detected.
509 modification is detected.
510 The record for each revision contains the first 4 bytes of the
510 The record for each revision contains the first 4 bytes of the
511 corresponding node hash, and the record is only used if it still matches.
511 corresponding node hash, and the record is only used if it still matches.
512 Even a completely trashed rbc-revs fill thus still give the right result
512 Even a completely trashed rbc-revs fill thus still give the right result
513 while converging towards full recovery ... assuming no incorrectly matching
513 while converging towards full recovery ... assuming no incorrectly matching
514 node hashes.
514 node hashes.
515 The record also contains 4 bytes where 31 bits contains the index of the
515 The record also contains 4 bytes where 31 bits contains the index of the
516 branch and the last bit indicate that it is a branch close commit.
516 branch and the last bit indicate that it is a branch close commit.
517 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
517 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
518 and will grow with it but be 1/8th of its size.
518 and will grow with it but be 1/8th of its size.
519 """
519 """
520
520
521 def __init__(self, repo, readonly=True):
521 def __init__(self, repo, readonly=True):
522 assert repo.filtername is None
522 assert repo.filtername is None
523 self._repo = repo
523 self._repo = repo
524 self._names = [] # branch names in local encoding with static index
524 self._names = [] # branch names in local encoding with static index
525 self._rbcrevs = bytearray()
525 self._rbcrevs = bytearray()
526 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
526 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
527 try:
527 try:
528 bndata = repo.cachevfs.read(_rbcnames)
528 bndata = repo.cachevfs.read(_rbcnames)
529 self._rbcsnameslen = len(bndata) # for verification before writing
529 self._rbcsnameslen = len(bndata) # for verification before writing
530 if bndata:
530 if bndata:
531 self._names = [
531 self._names = [
532 encoding.tolocal(bn) for bn in bndata.split(b'\0')
532 encoding.tolocal(bn) for bn in bndata.split(b'\0')
533 ]
533 ]
534 except (IOError, OSError):
534 except (IOError, OSError):
535 if readonly:
535 if readonly:
536 # don't try to use cache - fall back to the slow path
536 # don't try to use cache - fall back to the slow path
537 self.branchinfo = self._branchinfo
537 self.branchinfo = self._branchinfo
538
538
539 if self._names:
539 if self._names:
540 try:
540 try:
541 data = repo.cachevfs.read(_rbcrevs)
541 data = repo.cachevfs.read(_rbcrevs)
542 self._rbcrevs[:] = data
542 self._rbcrevs[:] = data
543 except (IOError, OSError) as inst:
543 except (IOError, OSError) as inst:
544 repo.ui.debug(
544 repo.ui.debug(
545 b"couldn't read revision branch cache: %s\n"
545 b"couldn't read revision branch cache: %s\n"
546 % stringutil.forcebytestr(inst)
546 % stringutil.forcebytestr(inst)
547 )
547 )
548 # remember number of good records on disk
548 # remember number of good records on disk
549 self._rbcrevslen = min(
549 self._rbcrevslen = min(
550 len(self._rbcrevs) // _rbcrecsize, len(repo.changelog)
550 len(self._rbcrevs) // _rbcrecsize, len(repo.changelog)
551 )
551 )
552 if self._rbcrevslen == 0:
552 if self._rbcrevslen == 0:
553 self._names = []
553 self._names = []
554 self._rbcnamescount = len(self._names) # number of names read at
554 self._rbcnamescount = len(self._names) # number of names read at
555 # _rbcsnameslen
555 # _rbcsnameslen
556
556
557 def _clear(self):
557 def _clear(self):
558 self._rbcsnameslen = 0
558 self._rbcsnameslen = 0
559 del self._names[:]
559 del self._names[:]
560 self._rbcnamescount = 0
560 self._rbcnamescount = 0
561 self._rbcrevslen = len(self._repo.changelog)
561 self._rbcrevslen = len(self._repo.changelog)
562 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
562 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
563 util.clearcachedproperty(self, b'_namesreverse')
563 util.clearcachedproperty(self, b'_namesreverse')
564
564
565 @util.propertycache
565 @util.propertycache
566 def _namesreverse(self):
566 def _namesreverse(self):
567 return dict((b, r) for r, b in enumerate(self._names))
567 return dict((b, r) for r, b in enumerate(self._names))
568
568
569 def branchinfo(self, rev):
569 def branchinfo(self, rev):
570 """Return branch name and close flag for rev, using and updating
570 """Return branch name and close flag for rev, using and updating
571 persistent cache."""
571 persistent cache."""
572 changelog = self._repo.changelog
572 changelog = self._repo.changelog
573 rbcrevidx = rev * _rbcrecsize
573 rbcrevidx = rev * _rbcrecsize
574
574
575 # avoid negative index, changelog.read(nullrev) is fast without cache
575 # avoid negative index, changelog.read(nullrev) is fast without cache
576 if rev == nullrev:
576 if rev == nullrev:
577 return changelog.branchinfo(rev)
577 return changelog.branchinfo(rev)
578
578
579 # if requested rev isn't allocated, grow and cache the rev info
579 # if requested rev isn't allocated, grow and cache the rev info
580 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
580 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
581 return self._branchinfo(rev)
581 return self._branchinfo(rev)
582
582
583 # fast path: extract data from cache, use it if node is matching
583 # fast path: extract data from cache, use it if node is matching
584 reponode = changelog.node(rev)[:_rbcnodelen]
584 reponode = changelog.node(rev)[:_rbcnodelen]
585 cachenode, branchidx = unpack_from(
585 cachenode, branchidx = unpack_from(
586 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx
586 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx
587 )
587 )
588 close = bool(branchidx & _rbccloseflag)
588 close = bool(branchidx & _rbccloseflag)
589 if close:
589 if close:
590 branchidx &= _rbcbranchidxmask
590 branchidx &= _rbcbranchidxmask
591 if cachenode == b'\0\0\0\0':
591 if cachenode == b'\0\0\0\0':
592 pass
592 pass
593 elif cachenode == reponode:
593 elif cachenode == reponode:
594 try:
594 try:
595 return self._names[branchidx], close
595 return self._names[branchidx], close
596 except IndexError:
596 except IndexError:
597 # recover from invalid reference to unknown branch
597 # recover from invalid reference to unknown branch
598 self._repo.ui.debug(
598 self._repo.ui.debug(
599 b"referenced branch names not found"
599 b"referenced branch names not found"
600 b" - rebuilding revision branch cache from scratch\n"
600 b" - rebuilding revision branch cache from scratch\n"
601 )
601 )
602 self._clear()
602 self._clear()
603 else:
603 else:
604 # rev/node map has changed, invalidate the cache from here up
604 # rev/node map has changed, invalidate the cache from here up
605 self._repo.ui.debug(
605 self._repo.ui.debug(
606 b"history modification detected - truncating "
606 b"history modification detected - truncating "
607 b"revision branch cache to revision %d\n" % rev
607 b"revision branch cache to revision %d\n" % rev
608 )
608 )
609 truncate = rbcrevidx + _rbcrecsize
609 truncate = rbcrevidx + _rbcrecsize
610 del self._rbcrevs[truncate:]
610 del self._rbcrevs[truncate:]
611 self._rbcrevslen = min(self._rbcrevslen, truncate)
611 self._rbcrevslen = min(self._rbcrevslen, truncate)
612
612
613 # fall back to slow path and make sure it will be written to disk
613 # fall back to slow path and make sure it will be written to disk
614 return self._branchinfo(rev)
614 return self._branchinfo(rev)
615
615
616 def _branchinfo(self, rev):
616 def _branchinfo(self, rev):
617 """Retrieve branch info from changelog and update _rbcrevs"""
617 """Retrieve branch info from changelog and update _rbcrevs"""
618 changelog = self._repo.changelog
618 changelog = self._repo.changelog
619 b, close = changelog.branchinfo(rev)
619 b, close = changelog.branchinfo(rev)
620 if b in self._namesreverse:
620 if b in self._namesreverse:
621 branchidx = self._namesreverse[b]
621 branchidx = self._namesreverse[b]
622 else:
622 else:
623 branchidx = len(self._names)
623 branchidx = len(self._names)
624 self._names.append(b)
624 self._names.append(b)
625 self._namesreverse[b] = branchidx
625 self._namesreverse[b] = branchidx
626 reponode = changelog.node(rev)
626 reponode = changelog.node(rev)
627 if close:
627 if close:
628 branchidx |= _rbccloseflag
628 branchidx |= _rbccloseflag
629 self._setcachedata(rev, reponode, branchidx)
629 self._setcachedata(rev, reponode, branchidx)
630 return b, close
630 return b, close
631
631
632 def setdata(self, branch, rev, node, close):
632 def setdata(self, branch, rev, node, close):
633 """add new data information to the cache"""
633 """add new data information to the cache"""
634 if branch in self._namesreverse:
634 if branch in self._namesreverse:
635 branchidx = self._namesreverse[branch]
635 branchidx = self._namesreverse[branch]
636 else:
636 else:
637 branchidx = len(self._names)
637 branchidx = len(self._names)
638 self._names.append(branch)
638 self._names.append(branch)
639 self._namesreverse[branch] = branchidx
639 self._namesreverse[branch] = branchidx
640 if close:
640 if close:
641 branchidx |= _rbccloseflag
641 branchidx |= _rbccloseflag
642 self._setcachedata(rev, node, branchidx)
642 self._setcachedata(rev, node, branchidx)
643 # If no cache data were readable (non exists, bad permission, etc)
643 # If no cache data were readable (non exists, bad permission, etc)
644 # the cache was bypassing itself by setting:
644 # the cache was bypassing itself by setting:
645 #
645 #
646 # self.branchinfo = self._branchinfo
646 # self.branchinfo = self._branchinfo
647 #
647 #
648 # Since we now have data in the cache, we need to drop this bypassing.
648 # Since we now have data in the cache, we need to drop this bypassing.
649 if 'branchinfo' in vars(self):
649 if 'branchinfo' in vars(self):
650 del self.branchinfo
650 del self.branchinfo
651
651
652 def _setcachedata(self, rev, node, branchidx):
652 def _setcachedata(self, rev, node, branchidx):
653 """Writes the node's branch data to the in-memory cache data."""
653 """Writes the node's branch data to the in-memory cache data."""
654 if rev == nullrev:
654 if rev == nullrev:
655 return
655 return
656 rbcrevidx = rev * _rbcrecsize
656 rbcrevidx = rev * _rbcrecsize
657 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
657 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
658 self._rbcrevs.extend(
658 self._rbcrevs.extend(
659 b'\0'
659 b'\0'
660 * (len(self._repo.changelog) * _rbcrecsize - len(self._rbcrevs))
660 * (len(self._repo.changelog) * _rbcrecsize - len(self._rbcrevs))
661 )
661 )
662 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
662 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
663 self._rbcrevslen = min(self._rbcrevslen, rev)
663 self._rbcrevslen = min(self._rbcrevslen, rev)
664
664
665 tr = self._repo.currenttransaction()
665 tr = self._repo.currenttransaction()
666 if tr:
666 if tr:
667 tr.addfinalize(b'write-revbranchcache', self.write)
667 tr.addfinalize(b'write-revbranchcache', self.write)
668
668
669 def write(self, tr=None):
669 def write(self, tr=None):
670 """Save branch cache if it is dirty."""
670 """Save branch cache if it is dirty."""
671 repo = self._repo
671 repo = self._repo
672 wlock = None
672 wlock = None
673 step = b''
673 step = b''
674 try:
674 try:
675 # write the new names
675 # write the new names
676 if self._rbcnamescount < len(self._names):
676 if self._rbcnamescount < len(self._names):
677 wlock = repo.wlock(wait=False)
677 wlock = repo.wlock(wait=False)
678 step = b' names'
678 step = b' names'
679 self._writenames(repo)
679 self._writenames(repo)
680
680
681 # write the new revs
681 # write the new revs
682 start = self._rbcrevslen * _rbcrecsize
682 start = self._rbcrevslen * _rbcrecsize
683 if start != len(self._rbcrevs):
683 if start != len(self._rbcrevs):
684 step = b''
684 step = b''
685 if wlock is None:
685 if wlock is None:
686 wlock = repo.wlock(wait=False)
686 wlock = repo.wlock(wait=False)
687 self._writerevs(repo, start)
687 self._writerevs(repo, start)
688
688
689 except (IOError, OSError, error.Abort, error.LockError) as inst:
689 except (IOError, OSError, error.Abort, error.LockError) as inst:
690 repo.ui.debug(
690 repo.ui.debug(
691 b"couldn't write revision branch cache%s: %s\n"
691 b"couldn't write revision branch cache%s: %s\n"
692 % (step, stringutil.forcebytestr(inst))
692 % (step, stringutil.forcebytestr(inst))
693 )
693 )
694 finally:
694 finally:
695 if wlock is not None:
695 if wlock is not None:
696 wlock.release()
696 wlock.release()
697
697
698 def _writenames(self, repo):
698 def _writenames(self, repo):
699 """ write the new branch names to revbranchcache """
699 """ write the new branch names to revbranchcache """
700 if self._rbcnamescount != 0:
700 if self._rbcnamescount != 0:
701 f = repo.cachevfs.open(_rbcnames, b'ab')
701 f = repo.cachevfs.open(_rbcnames, b'ab')
702 if f.tell() == self._rbcsnameslen:
702 if f.tell() == self._rbcsnameslen:
703 f.write(b'\0')
703 f.write(b'\0')
704 else:
704 else:
705 f.close()
705 f.close()
706 repo.ui.debug(b"%s changed - rewriting it\n" % _rbcnames)
706 repo.ui.debug(b"%s changed - rewriting it\n" % _rbcnames)
707 self._rbcnamescount = 0
707 self._rbcnamescount = 0
708 self._rbcrevslen = 0
708 self._rbcrevslen = 0
709 if self._rbcnamescount == 0:
709 if self._rbcnamescount == 0:
710 # before rewriting names, make sure references are removed
710 # before rewriting names, make sure references are removed
711 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
711 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
712 f = repo.cachevfs.open(_rbcnames, b'wb')
712 f = repo.cachevfs.open(_rbcnames, b'wb')
713 f.write(
713 f.write(
714 b'\0'.join(
714 b'\0'.join(
715 encoding.fromlocal(b)
715 encoding.fromlocal(b)
716 for b in self._names[self._rbcnamescount :]
716 for b in self._names[self._rbcnamescount :]
717 )
717 )
718 )
718 )
719 self._rbcsnameslen = f.tell()
719 self._rbcsnameslen = f.tell()
720 f.close()
720 f.close()
721 self._rbcnamescount = len(self._names)
721 self._rbcnamescount = len(self._names)
722
722
723 def _writerevs(self, repo, start):
723 def _writerevs(self, repo, start):
724 """ write the new revs to revbranchcache """
724 """ write the new revs to revbranchcache """
725 revs = min(len(repo.changelog), len(self._rbcrevs) // _rbcrecsize)
725 revs = min(len(repo.changelog), len(self._rbcrevs) // _rbcrecsize)
726 with repo.cachevfs.open(_rbcrevs, b'ab') as f:
726 with repo.cachevfs.open(_rbcrevs, b'ab') as f:
727 if f.tell() != start:
727 if f.tell() != start:
728 repo.ui.debug(
728 repo.ui.debug(
729 b"truncating cache/%s to %d\n" % (_rbcrevs, start)
729 b"truncating cache/%s to %d\n" % (_rbcrevs, start)
730 )
730 )
731 f.seek(start)
731 f.seek(start)
732 if f.tell() != start:
732 if f.tell() != start:
733 start = 0
733 start = 0
734 f.seek(start)
734 f.seek(start)
735 f.truncate()
735 f.truncate()
736 end = revs * _rbcrecsize
736 end = revs * _rbcrecsize
737 f.write(self._rbcrevs[start:end])
737 f.write(self._rbcrevs[start:end])
738 self._rbcrevslen = revs
738 self._rbcrevslen = revs
General Comments 0
You need to be logged in to leave comments. Login now