##// END OF EJS Templates
branchmap: fix python 2.6 by using util.buffer() instead of passing bytearray
Augie Fackler -
r31381:7359157b default
parent child Browse files
Show More
@@ -1,518 +1,519
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 scmutil,
21 scmutil,
22 util,
22 util,
23 )
23 )
24
24
25 calcsize = struct.calcsize
25 calcsize = struct.calcsize
26 pack_into = struct.pack_into
26 pack_into = struct.pack_into
27 unpack_from = struct.unpack_from
27 unpack_from = struct.unpack_from
28
28
29 def _filename(repo):
29 def _filename(repo):
30 """name of a branchcache file for a given repo or repoview"""
30 """name of a branchcache file for a given repo or repoview"""
31 filename = "cache/branch2"
31 filename = "cache/branch2"
32 if repo.filtername:
32 if repo.filtername:
33 filename = '%s-%s' % (filename, repo.filtername)
33 filename = '%s-%s' % (filename, repo.filtername)
34 return filename
34 return filename
35
35
36 def read(repo):
36 def read(repo):
37 try:
37 try:
38 f = repo.vfs(_filename(repo))
38 f = repo.vfs(_filename(repo))
39 lines = f.read().split('\n')
39 lines = f.read().split('\n')
40 f.close()
40 f.close()
41 except (IOError, OSError):
41 except (IOError, OSError):
42 return None
42 return None
43
43
44 try:
44 try:
45 cachekey = lines.pop(0).split(" ", 2)
45 cachekey = lines.pop(0).split(" ", 2)
46 last, lrev = cachekey[:2]
46 last, lrev = cachekey[:2]
47 last, lrev = bin(last), int(lrev)
47 last, lrev = bin(last), int(lrev)
48 filteredhash = None
48 filteredhash = None
49 if len(cachekey) > 2:
49 if len(cachekey) > 2:
50 filteredhash = bin(cachekey[2])
50 filteredhash = bin(cachekey[2])
51 partial = branchcache(tipnode=last, tiprev=lrev,
51 partial = branchcache(tipnode=last, tiprev=lrev,
52 filteredhash=filteredhash)
52 filteredhash=filteredhash)
53 if not partial.validfor(repo):
53 if not partial.validfor(repo):
54 # invalidate the cache
54 # invalidate the cache
55 raise ValueError('tip differs')
55 raise ValueError('tip differs')
56 cl = repo.changelog
56 cl = repo.changelog
57 for l in lines:
57 for l in lines:
58 if not l:
58 if not l:
59 continue
59 continue
60 node, state, label = l.split(" ", 2)
60 node, state, label = l.split(" ", 2)
61 if state not in 'oc':
61 if state not in 'oc':
62 raise ValueError('invalid branch state')
62 raise ValueError('invalid branch state')
63 label = encoding.tolocal(label.strip())
63 label = encoding.tolocal(label.strip())
64 node = bin(node)
64 node = bin(node)
65 if not cl.hasnode(node):
65 if not cl.hasnode(node):
66 raise ValueError('node %s does not exist' % hex(node))
66 raise ValueError('node %s does not exist' % hex(node))
67 partial.setdefault(label, []).append(node)
67 partial.setdefault(label, []).append(node)
68 if state == 'c':
68 if state == 'c':
69 partial._closednodes.add(node)
69 partial._closednodes.add(node)
70 except KeyboardInterrupt:
70 except KeyboardInterrupt:
71 raise
71 raise
72 except Exception as inst:
72 except Exception as inst:
73 if repo.ui.debugflag:
73 if repo.ui.debugflag:
74 msg = 'invalid branchheads cache'
74 msg = 'invalid branchheads cache'
75 if repo.filtername is not None:
75 if repo.filtername is not None:
76 msg += ' (%s)' % repo.filtername
76 msg += ' (%s)' % repo.filtername
77 msg += ': %s\n'
77 msg += ': %s\n'
78 repo.ui.debug(msg % inst)
78 repo.ui.debug(msg % inst)
79 partial = None
79 partial = None
80 return partial
80 return partial
81
81
82 ### Nearest subset relation
82 ### Nearest subset relation
83 # Nearest subset of filter X is a filter Y so that:
83 # Nearest subset of filter X is a filter Y so that:
84 # * Y is included in X,
84 # * Y is included in X,
85 # * X - Y is as small as possible.
85 # * X - Y is as small as possible.
86 # This create and ordering used for branchmap purpose.
86 # This create and ordering used for branchmap purpose.
87 # the ordering may be partial
87 # the ordering may be partial
88 subsettable = {None: 'visible',
88 subsettable = {None: 'visible',
89 'visible': 'served',
89 'visible': 'served',
90 'served': 'immutable',
90 'served': 'immutable',
91 'immutable': 'base'}
91 'immutable': 'base'}
92
92
93 def updatecache(repo):
93 def updatecache(repo):
94 cl = repo.changelog
94 cl = repo.changelog
95 filtername = repo.filtername
95 filtername = repo.filtername
96 partial = repo._branchcaches.get(filtername)
96 partial = repo._branchcaches.get(filtername)
97
97
98 revs = []
98 revs = []
99 if partial is None or not partial.validfor(repo):
99 if partial is None or not partial.validfor(repo):
100 partial = read(repo)
100 partial = read(repo)
101 if partial is None:
101 if partial is None:
102 subsetname = subsettable.get(filtername)
102 subsetname = subsettable.get(filtername)
103 if subsetname is None:
103 if subsetname is None:
104 partial = branchcache()
104 partial = branchcache()
105 else:
105 else:
106 subset = repo.filtered(subsetname)
106 subset = repo.filtered(subsetname)
107 partial = subset.branchmap().copy()
107 partial = subset.branchmap().copy()
108 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
108 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
109 revs.extend(r for r in extrarevs if r <= partial.tiprev)
109 revs.extend(r for r in extrarevs if r <= partial.tiprev)
110 revs.extend(cl.revs(start=partial.tiprev + 1))
110 revs.extend(cl.revs(start=partial.tiprev + 1))
111 if revs:
111 if revs:
112 partial.update(repo, revs)
112 partial.update(repo, revs)
113 partial.write(repo)
113 partial.write(repo)
114
114
115 assert partial.validfor(repo), filtername
115 assert partial.validfor(repo), filtername
116 repo._branchcaches[repo.filtername] = partial
116 repo._branchcaches[repo.filtername] = partial
117
117
118 def replacecache(repo, bm):
118 def replacecache(repo, bm):
119 """Replace the branchmap cache for a repo with a branch mapping.
119 """Replace the branchmap cache for a repo with a branch mapping.
120
120
121 This is likely only called during clone with a branch map from a remote.
121 This is likely only called during clone with a branch map from a remote.
122 """
122 """
123 rbheads = []
123 rbheads = []
124 closed = []
124 closed = []
125 for bheads in bm.itervalues():
125 for bheads in bm.itervalues():
126 rbheads.extend(bheads)
126 rbheads.extend(bheads)
127 for h in bheads:
127 for h in bheads:
128 r = repo.changelog.rev(h)
128 r = repo.changelog.rev(h)
129 b, c = repo.changelog.branchinfo(r)
129 b, c = repo.changelog.branchinfo(r)
130 if c:
130 if c:
131 closed.append(h)
131 closed.append(h)
132
132
133 if rbheads:
133 if rbheads:
134 rtiprev = max((int(repo.changelog.rev(node))
134 rtiprev = max((int(repo.changelog.rev(node))
135 for node in rbheads))
135 for node in rbheads))
136 cache = branchcache(bm,
136 cache = branchcache(bm,
137 repo[rtiprev].node(),
137 repo[rtiprev].node(),
138 rtiprev,
138 rtiprev,
139 closednodes=closed)
139 closednodes=closed)
140
140
141 # Try to stick it as low as possible
141 # Try to stick it as low as possible
142 # filter above served are unlikely to be fetch from a clone
142 # filter above served are unlikely to be fetch from a clone
143 for candidate in ('base', 'immutable', 'served'):
143 for candidate in ('base', 'immutable', 'served'):
144 rview = repo.filtered(candidate)
144 rview = repo.filtered(candidate)
145 if cache.validfor(rview):
145 if cache.validfor(rview):
146 repo._branchcaches[candidate] = cache
146 repo._branchcaches[candidate] = cache
147 cache.write(rview)
147 cache.write(rview)
148 break
148 break
149
149
150 class branchcache(dict):
150 class branchcache(dict):
151 """A dict like object that hold branches heads cache.
151 """A dict like object that hold branches heads cache.
152
152
153 This cache is used to avoid costly computations to determine all the
153 This cache is used to avoid costly computations to determine all the
154 branch heads of a repo.
154 branch heads of a repo.
155
155
156 The cache is serialized on disk in the following format:
156 The cache is serialized on disk in the following format:
157
157
158 <tip hex node> <tip rev number> [optional filtered repo hex hash]
158 <tip hex node> <tip rev number> [optional filtered repo hex hash]
159 <branch head hex node> <open/closed state> <branch name>
159 <branch head hex node> <open/closed state> <branch name>
160 <branch head hex node> <open/closed state> <branch name>
160 <branch head hex node> <open/closed state> <branch name>
161 ...
161 ...
162
162
163 The first line is used to check if the cache is still valid. If the
163 The first line is used to check if the cache is still valid. If the
164 branch cache is for a filtered repo view, an optional third hash is
164 branch cache is for a filtered repo view, an optional third hash is
165 included that hashes the hashes of all filtered revisions.
165 included that hashes the hashes of all filtered revisions.
166
166
167 The open/closed state is represented by a single letter 'o' or 'c'.
167 The open/closed state is represented by a single letter 'o' or 'c'.
168 This field can be used to avoid changelog reads when determining if a
168 This field can be used to avoid changelog reads when determining if a
169 branch head closes a branch or not.
169 branch head closes a branch or not.
170 """
170 """
171
171
172 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
172 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
173 filteredhash=None, closednodes=None):
173 filteredhash=None, closednodes=None):
174 super(branchcache, self).__init__(entries)
174 super(branchcache, self).__init__(entries)
175 self.tipnode = tipnode
175 self.tipnode = tipnode
176 self.tiprev = tiprev
176 self.tiprev = tiprev
177 self.filteredhash = filteredhash
177 self.filteredhash = filteredhash
178 # closednodes is a set of nodes that close their branch. If the branch
178 # closednodes is a set of nodes that close their branch. If the branch
179 # cache has been updated, it may contain nodes that are no longer
179 # cache has been updated, it may contain nodes that are no longer
180 # heads.
180 # heads.
181 if closednodes is None:
181 if closednodes is None:
182 self._closednodes = set()
182 self._closednodes = set()
183 else:
183 else:
184 self._closednodes = closednodes
184 self._closednodes = closednodes
185
185
186 def validfor(self, repo):
186 def validfor(self, repo):
187 """Is the cache content valid regarding a repo
187 """Is the cache content valid regarding a repo
188
188
189 - False when cached tipnode is unknown or if we detect a strip.
189 - False when cached tipnode is unknown or if we detect a strip.
190 - True when cache is up to date or a subset of current repo."""
190 - True when cache is up to date or a subset of current repo."""
191 try:
191 try:
192 return ((self.tipnode == repo.changelog.node(self.tiprev))
192 return ((self.tipnode == repo.changelog.node(self.tiprev))
193 and (self.filteredhash == \
193 and (self.filteredhash == \
194 scmutil.filteredhash(repo, self.tiprev)))
194 scmutil.filteredhash(repo, self.tiprev)))
195 except IndexError:
195 except IndexError:
196 return False
196 return False
197
197
198 def _branchtip(self, heads):
198 def _branchtip(self, heads):
199 '''Return tuple with last open head in heads and false,
199 '''Return tuple with last open head in heads and false,
200 otherwise return last closed head and true.'''
200 otherwise return last closed head and true.'''
201 tip = heads[-1]
201 tip = heads[-1]
202 closed = True
202 closed = True
203 for h in reversed(heads):
203 for h in reversed(heads):
204 if h not in self._closednodes:
204 if h not in self._closednodes:
205 tip = h
205 tip = h
206 closed = False
206 closed = False
207 break
207 break
208 return tip, closed
208 return tip, closed
209
209
210 def branchtip(self, branch):
210 def branchtip(self, branch):
211 '''Return the tipmost open head on branch head, otherwise return the
211 '''Return the tipmost open head on branch head, otherwise return the
212 tipmost closed head on branch.
212 tipmost closed head on branch.
213 Raise KeyError for unknown branch.'''
213 Raise KeyError for unknown branch.'''
214 return self._branchtip(self[branch])[0]
214 return self._branchtip(self[branch])[0]
215
215
216 def branchheads(self, branch, closed=False):
216 def branchheads(self, branch, closed=False):
217 heads = self[branch]
217 heads = self[branch]
218 if not closed:
218 if not closed:
219 heads = [h for h in heads if h not in self._closednodes]
219 heads = [h for h in heads if h not in self._closednodes]
220 return heads
220 return heads
221
221
222 def iterbranches(self):
222 def iterbranches(self):
223 for bn, heads in self.iteritems():
223 for bn, heads in self.iteritems():
224 yield (bn, heads) + self._branchtip(heads)
224 yield (bn, heads) + self._branchtip(heads)
225
225
226 def copy(self):
226 def copy(self):
227 """return an deep copy of the branchcache object"""
227 """return an deep copy of the branchcache object"""
228 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash,
228 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash,
229 self._closednodes)
229 self._closednodes)
230
230
231 def write(self, repo):
231 def write(self, repo):
232 try:
232 try:
233 f = repo.vfs(_filename(repo), "w", atomictemp=True)
233 f = repo.vfs(_filename(repo), "w", atomictemp=True)
234 cachekey = [hex(self.tipnode), '%d' % self.tiprev]
234 cachekey = [hex(self.tipnode), '%d' % self.tiprev]
235 if self.filteredhash is not None:
235 if self.filteredhash is not None:
236 cachekey.append(hex(self.filteredhash))
236 cachekey.append(hex(self.filteredhash))
237 f.write(" ".join(cachekey) + '\n')
237 f.write(" ".join(cachekey) + '\n')
238 nodecount = 0
238 nodecount = 0
239 for label, nodes in sorted(self.iteritems()):
239 for label, nodes in sorted(self.iteritems()):
240 for node in nodes:
240 for node in nodes:
241 nodecount += 1
241 nodecount += 1
242 if node in self._closednodes:
242 if node in self._closednodes:
243 state = 'c'
243 state = 'c'
244 else:
244 else:
245 state = 'o'
245 state = 'o'
246 f.write("%s %s %s\n" % (hex(node), state,
246 f.write("%s %s %s\n" % (hex(node), state,
247 encoding.fromlocal(label)))
247 encoding.fromlocal(label)))
248 f.close()
248 f.close()
249 repo.ui.log('branchcache',
249 repo.ui.log('branchcache',
250 'wrote %s branch cache with %d labels and %d nodes\n',
250 'wrote %s branch cache with %d labels and %d nodes\n',
251 repo.filtername, len(self), nodecount)
251 repo.filtername, len(self), nodecount)
252 except (IOError, OSError, error.Abort) as inst:
252 except (IOError, OSError, error.Abort) as inst:
253 repo.ui.debug("couldn't write branch cache: %s\n" % inst)
253 repo.ui.debug("couldn't write branch cache: %s\n" % inst)
254 # Abort may be raise by read only opener
254 # Abort may be raise by read only opener
255 pass
255 pass
256
256
257 def update(self, repo, revgen):
257 def update(self, repo, revgen):
258 """Given a branchhead cache, self, that may have extra nodes or be
258 """Given a branchhead cache, self, that may have extra nodes or be
259 missing heads, and a generator of nodes that are strictly a superset of
259 missing heads, and a generator of nodes that are strictly a superset of
260 heads missing, this function updates self to be correct.
260 heads missing, this function updates self to be correct.
261 """
261 """
262 starttime = util.timer()
262 starttime = util.timer()
263 cl = repo.changelog
263 cl = repo.changelog
264 # collect new branch entries
264 # collect new branch entries
265 newbranches = {}
265 newbranches = {}
266 getbranchinfo = repo.revbranchcache().branchinfo
266 getbranchinfo = repo.revbranchcache().branchinfo
267 for r in revgen:
267 for r in revgen:
268 branch, closesbranch = getbranchinfo(r)
268 branch, closesbranch = getbranchinfo(r)
269 newbranches.setdefault(branch, []).append(r)
269 newbranches.setdefault(branch, []).append(r)
270 if closesbranch:
270 if closesbranch:
271 self._closednodes.add(cl.node(r))
271 self._closednodes.add(cl.node(r))
272
272
273 # fetch current topological heads to speed up filtering
273 # fetch current topological heads to speed up filtering
274 topoheads = set(cl.headrevs())
274 topoheads = set(cl.headrevs())
275
275
276 # if older branchheads are reachable from new ones, they aren't
276 # if older branchheads are reachable from new ones, they aren't
277 # really branchheads. Note checking parents is insufficient:
277 # really branchheads. Note checking parents is insufficient:
278 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
278 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
279 for branch, newheadrevs in newbranches.iteritems():
279 for branch, newheadrevs in newbranches.iteritems():
280 bheads = self.setdefault(branch, [])
280 bheads = self.setdefault(branch, [])
281 bheadset = set(cl.rev(node) for node in bheads)
281 bheadset = set(cl.rev(node) for node in bheads)
282
282
283 # This have been tested True on all internal usage of this function.
283 # This have been tested True on all internal usage of this function.
284 # run it again in case of doubt
284 # run it again in case of doubt
285 # assert not (set(bheadrevs) & set(newheadrevs))
285 # assert not (set(bheadrevs) & set(newheadrevs))
286 newheadrevs.sort()
286 newheadrevs.sort()
287 bheadset.update(newheadrevs)
287 bheadset.update(newheadrevs)
288
288
289 # This prunes out two kinds of heads - heads that are superseded by
289 # This prunes out two kinds of heads - heads that are superseded by
290 # a head in newheadrevs, and newheadrevs that are not heads because
290 # a head in newheadrevs, and newheadrevs that are not heads because
291 # an existing head is their descendant.
291 # an existing head is their descendant.
292 uncertain = bheadset - topoheads
292 uncertain = bheadset - topoheads
293 if uncertain:
293 if uncertain:
294 floorrev = min(uncertain)
294 floorrev = min(uncertain)
295 ancestors = set(cl.ancestors(newheadrevs, floorrev))
295 ancestors = set(cl.ancestors(newheadrevs, floorrev))
296 bheadset -= ancestors
296 bheadset -= ancestors
297 bheadrevs = sorted(bheadset)
297 bheadrevs = sorted(bheadset)
298 self[branch] = [cl.node(rev) for rev in bheadrevs]
298 self[branch] = [cl.node(rev) for rev in bheadrevs]
299 tiprev = bheadrevs[-1]
299 tiprev = bheadrevs[-1]
300 if tiprev > self.tiprev:
300 if tiprev > self.tiprev:
301 self.tipnode = cl.node(tiprev)
301 self.tipnode = cl.node(tiprev)
302 self.tiprev = tiprev
302 self.tiprev = tiprev
303
303
304 if not self.validfor(repo):
304 if not self.validfor(repo):
305 # cache key are not valid anymore
305 # cache key are not valid anymore
306 self.tipnode = nullid
306 self.tipnode = nullid
307 self.tiprev = nullrev
307 self.tiprev = nullrev
308 for heads in self.values():
308 for heads in self.values():
309 tiprev = max(cl.rev(node) for node in heads)
309 tiprev = max(cl.rev(node) for node in heads)
310 if tiprev > self.tiprev:
310 if tiprev > self.tiprev:
311 self.tipnode = cl.node(tiprev)
311 self.tipnode = cl.node(tiprev)
312 self.tiprev = tiprev
312 self.tiprev = tiprev
313 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
313 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
314
314
315 duration = util.timer() - starttime
315 duration = util.timer() - starttime
316 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n',
316 repo.ui.log('branchcache', 'updated %s branch cache in %.4f seconds\n',
317 repo.filtername, duration)
317 repo.filtername, duration)
318
318
319 # Revision branch info cache
319 # Revision branch info cache
320
320
321 _rbcversion = '-v1'
321 _rbcversion = '-v1'
322 _rbcnames = 'cache/rbc-names' + _rbcversion
322 _rbcnames = 'cache/rbc-names' + _rbcversion
323 _rbcrevs = 'cache/rbc-revs' + _rbcversion
323 _rbcrevs = 'cache/rbc-revs' + _rbcversion
324 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
324 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
325 _rbcrecfmt = '>4sI'
325 _rbcrecfmt = '>4sI'
326 _rbcrecsize = calcsize(_rbcrecfmt)
326 _rbcrecsize = calcsize(_rbcrecfmt)
327 _rbcnodelen = 4
327 _rbcnodelen = 4
328 _rbcbranchidxmask = 0x7fffffff
328 _rbcbranchidxmask = 0x7fffffff
329 _rbccloseflag = 0x80000000
329 _rbccloseflag = 0x80000000
330
330
331 class revbranchcache(object):
331 class revbranchcache(object):
332 """Persistent cache, mapping from revision number to branch name and close.
332 """Persistent cache, mapping from revision number to branch name and close.
333 This is a low level cache, independent of filtering.
333 This is a low level cache, independent of filtering.
334
334
335 Branch names are stored in rbc-names in internal encoding separated by 0.
335 Branch names are stored in rbc-names in internal encoding separated by 0.
336 rbc-names is append-only, and each branch name is only stored once and will
336 rbc-names is append-only, and each branch name is only stored once and will
337 thus have a unique index.
337 thus have a unique index.
338
338
339 The branch info for each revision is stored in rbc-revs as constant size
339 The branch info for each revision is stored in rbc-revs as constant size
340 records. The whole file is read into memory, but it is only 'parsed' on
340 records. The whole file is read into memory, but it is only 'parsed' on
341 demand. The file is usually append-only but will be truncated if repo
341 demand. The file is usually append-only but will be truncated if repo
342 modification is detected.
342 modification is detected.
343 The record for each revision contains the first 4 bytes of the
343 The record for each revision contains the first 4 bytes of the
344 corresponding node hash, and the record is only used if it still matches.
344 corresponding node hash, and the record is only used if it still matches.
345 Even a completely trashed rbc-revs fill thus still give the right result
345 Even a completely trashed rbc-revs fill thus still give the right result
346 while converging towards full recovery ... assuming no incorrectly matching
346 while converging towards full recovery ... assuming no incorrectly matching
347 node hashes.
347 node hashes.
348 The record also contains 4 bytes where 31 bits contains the index of the
348 The record also contains 4 bytes where 31 bits contains the index of the
349 branch and the last bit indicate that it is a branch close commit.
349 branch and the last bit indicate that it is a branch close commit.
350 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
350 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
351 and will grow with it but be 1/8th of its size.
351 and will grow with it but be 1/8th of its size.
352 """
352 """
353
353
354 def __init__(self, repo, readonly=True):
354 def __init__(self, repo, readonly=True):
355 assert repo.filtername is None
355 assert repo.filtername is None
356 self._repo = repo
356 self._repo = repo
357 self._names = [] # branch names in local encoding with static index
357 self._names = [] # branch names in local encoding with static index
358 self._rbcrevs = bytearray()
358 self._rbcrevs = bytearray()
359 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
359 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
360 try:
360 try:
361 bndata = repo.vfs.read(_rbcnames)
361 bndata = repo.vfs.read(_rbcnames)
362 self._rbcsnameslen = len(bndata) # for verification before writing
362 self._rbcsnameslen = len(bndata) # for verification before writing
363 if bndata:
363 if bndata:
364 self._names = [encoding.tolocal(bn)
364 self._names = [encoding.tolocal(bn)
365 for bn in bndata.split('\0')]
365 for bn in bndata.split('\0')]
366 except (IOError, OSError):
366 except (IOError, OSError):
367 if readonly:
367 if readonly:
368 # don't try to use cache - fall back to the slow path
368 # don't try to use cache - fall back to the slow path
369 self.branchinfo = self._branchinfo
369 self.branchinfo = self._branchinfo
370
370
371 if self._names:
371 if self._names:
372 try:
372 try:
373 data = repo.vfs.read(_rbcrevs)
373 data = repo.vfs.read(_rbcrevs)
374 self._rbcrevs[:] = data
374 self._rbcrevs[:] = data
375 except (IOError, OSError) as inst:
375 except (IOError, OSError) as inst:
376 repo.ui.debug("couldn't read revision branch cache: %s\n" %
376 repo.ui.debug("couldn't read revision branch cache: %s\n" %
377 inst)
377 inst)
378 # remember number of good records on disk
378 # remember number of good records on disk
379 self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize,
379 self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize,
380 len(repo.changelog))
380 len(repo.changelog))
381 if self._rbcrevslen == 0:
381 if self._rbcrevslen == 0:
382 self._names = []
382 self._names = []
383 self._rbcnamescount = len(self._names) # number of names read at
383 self._rbcnamescount = len(self._names) # number of names read at
384 # _rbcsnameslen
384 # _rbcsnameslen
385 self._namesreverse = dict((b, r) for r, b in enumerate(self._names))
385 self._namesreverse = dict((b, r) for r, b in enumerate(self._names))
386
386
387 def _clear(self):
387 def _clear(self):
388 self._rbcsnameslen = 0
388 self._rbcsnameslen = 0
389 del self._names[:]
389 del self._names[:]
390 self._rbcnamescount = 0
390 self._rbcnamescount = 0
391 self._namesreverse.clear()
391 self._namesreverse.clear()
392 self._rbcrevslen = len(self._repo.changelog)
392 self._rbcrevslen = len(self._repo.changelog)
393 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
393 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
394
394
395 def branchinfo(self, rev):
395 def branchinfo(self, rev):
396 """Return branch name and close flag for rev, using and updating
396 """Return branch name and close flag for rev, using and updating
397 persistent cache."""
397 persistent cache."""
398 changelog = self._repo.changelog
398 changelog = self._repo.changelog
399 rbcrevidx = rev * _rbcrecsize
399 rbcrevidx = rev * _rbcrecsize
400
400
401 # avoid negative index, changelog.read(nullrev) is fast without cache
401 # avoid negative index, changelog.read(nullrev) is fast without cache
402 if rev == nullrev:
402 if rev == nullrev:
403 return changelog.branchinfo(rev)
403 return changelog.branchinfo(rev)
404
404
405 # if requested rev isn't allocated, grow and cache the rev info
405 # if requested rev isn't allocated, grow and cache the rev info
406 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
406 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
407 return self._branchinfo(rev)
407 return self._branchinfo(rev)
408
408
409 # fast path: extract data from cache, use it if node is matching
409 # fast path: extract data from cache, use it if node is matching
410 reponode = changelog.node(rev)[:_rbcnodelen]
410 reponode = changelog.node(rev)[:_rbcnodelen]
411 cachenode, branchidx = unpack_from(_rbcrecfmt, self._rbcrevs, rbcrevidx)
411 cachenode, branchidx = unpack_from(
412 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx)
412 close = bool(branchidx & _rbccloseflag)
413 close = bool(branchidx & _rbccloseflag)
413 if close:
414 if close:
414 branchidx &= _rbcbranchidxmask
415 branchidx &= _rbcbranchidxmask
415 if cachenode == '\0\0\0\0':
416 if cachenode == '\0\0\0\0':
416 pass
417 pass
417 elif cachenode == reponode:
418 elif cachenode == reponode:
418 try:
419 try:
419 return self._names[branchidx], close
420 return self._names[branchidx], close
420 except IndexError:
421 except IndexError:
421 # recover from invalid reference to unknown branch
422 # recover from invalid reference to unknown branch
422 self._repo.ui.debug("referenced branch names not found"
423 self._repo.ui.debug("referenced branch names not found"
423 " - rebuilding revision branch cache from scratch\n")
424 " - rebuilding revision branch cache from scratch\n")
424 self._clear()
425 self._clear()
425 else:
426 else:
426 # rev/node map has changed, invalidate the cache from here up
427 # rev/node map has changed, invalidate the cache from here up
427 self._repo.ui.debug("history modification detected - truncating "
428 self._repo.ui.debug("history modification detected - truncating "
428 "revision branch cache to revision %s\n" % rev)
429 "revision branch cache to revision %s\n" % rev)
429 truncate = rbcrevidx + _rbcrecsize
430 truncate = rbcrevidx + _rbcrecsize
430 del self._rbcrevs[truncate:]
431 del self._rbcrevs[truncate:]
431 self._rbcrevslen = min(self._rbcrevslen, truncate)
432 self._rbcrevslen = min(self._rbcrevslen, truncate)
432
433
433 # fall back to slow path and make sure it will be written to disk
434 # fall back to slow path and make sure it will be written to disk
434 return self._branchinfo(rev)
435 return self._branchinfo(rev)
435
436
436 def _branchinfo(self, rev):
437 def _branchinfo(self, rev):
437 """Retrieve branch info from changelog and update _rbcrevs"""
438 """Retrieve branch info from changelog and update _rbcrevs"""
438 changelog = self._repo.changelog
439 changelog = self._repo.changelog
439 b, close = changelog.branchinfo(rev)
440 b, close = changelog.branchinfo(rev)
440 if b in self._namesreverse:
441 if b in self._namesreverse:
441 branchidx = self._namesreverse[b]
442 branchidx = self._namesreverse[b]
442 else:
443 else:
443 branchidx = len(self._names)
444 branchidx = len(self._names)
444 self._names.append(b)
445 self._names.append(b)
445 self._namesreverse[b] = branchidx
446 self._namesreverse[b] = branchidx
446 reponode = changelog.node(rev)
447 reponode = changelog.node(rev)
447 if close:
448 if close:
448 branchidx |= _rbccloseflag
449 branchidx |= _rbccloseflag
449 self._setcachedata(rev, reponode, branchidx)
450 self._setcachedata(rev, reponode, branchidx)
450 return b, close
451 return b, close
451
452
452 def _setcachedata(self, rev, node, branchidx):
453 def _setcachedata(self, rev, node, branchidx):
453 """Writes the node's branch data to the in-memory cache data."""
454 """Writes the node's branch data to the in-memory cache data."""
454 rbcrevidx = rev * _rbcrecsize
455 rbcrevidx = rev * _rbcrecsize
455 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
456 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
456 self._rbcrevs.extend('\0' *
457 self._rbcrevs.extend('\0' *
457 (len(self._repo.changelog) * _rbcrecsize -
458 (len(self._repo.changelog) * _rbcrecsize -
458 len(self._rbcrevs)))
459 len(self._rbcrevs)))
459 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
460 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
460 self._rbcrevslen = min(self._rbcrevslen, rev)
461 self._rbcrevslen = min(self._rbcrevslen, rev)
461
462
462 tr = self._repo.currenttransaction()
463 tr = self._repo.currenttransaction()
463 if tr:
464 if tr:
464 tr.addfinalize('write-revbranchcache', self.write)
465 tr.addfinalize('write-revbranchcache', self.write)
465
466
466 def write(self, tr=None):
467 def write(self, tr=None):
467 """Save branch cache if it is dirty."""
468 """Save branch cache if it is dirty."""
468 repo = self._repo
469 repo = self._repo
469 wlock = None
470 wlock = None
470 step = ''
471 step = ''
471 try:
472 try:
472 if self._rbcnamescount < len(self._names):
473 if self._rbcnamescount < len(self._names):
473 step = ' names'
474 step = ' names'
474 wlock = repo.wlock(wait=False)
475 wlock = repo.wlock(wait=False)
475 if self._rbcnamescount != 0:
476 if self._rbcnamescount != 0:
476 f = repo.vfs.open(_rbcnames, 'ab')
477 f = repo.vfs.open(_rbcnames, 'ab')
477 if f.tell() == self._rbcsnameslen:
478 if f.tell() == self._rbcsnameslen:
478 f.write('\0')
479 f.write('\0')
479 else:
480 else:
480 f.close()
481 f.close()
481 repo.ui.debug("%s changed - rewriting it\n" % _rbcnames)
482 repo.ui.debug("%s changed - rewriting it\n" % _rbcnames)
482 self._rbcnamescount = 0
483 self._rbcnamescount = 0
483 self._rbcrevslen = 0
484 self._rbcrevslen = 0
484 if self._rbcnamescount == 0:
485 if self._rbcnamescount == 0:
485 # before rewriting names, make sure references are removed
486 # before rewriting names, make sure references are removed
486 repo.vfs.unlinkpath(_rbcrevs, ignoremissing=True)
487 repo.vfs.unlinkpath(_rbcrevs, ignoremissing=True)
487 f = repo.vfs.open(_rbcnames, 'wb')
488 f = repo.vfs.open(_rbcnames, 'wb')
488 f.write('\0'.join(encoding.fromlocal(b)
489 f.write('\0'.join(encoding.fromlocal(b)
489 for b in self._names[self._rbcnamescount:]))
490 for b in self._names[self._rbcnamescount:]))
490 self._rbcsnameslen = f.tell()
491 self._rbcsnameslen = f.tell()
491 f.close()
492 f.close()
492 self._rbcnamescount = len(self._names)
493 self._rbcnamescount = len(self._names)
493
494
494 start = self._rbcrevslen * _rbcrecsize
495 start = self._rbcrevslen * _rbcrecsize
495 if start != len(self._rbcrevs):
496 if start != len(self._rbcrevs):
496 step = ''
497 step = ''
497 if wlock is None:
498 if wlock is None:
498 wlock = repo.wlock(wait=False)
499 wlock = repo.wlock(wait=False)
499 revs = min(len(repo.changelog),
500 revs = min(len(repo.changelog),
500 len(self._rbcrevs) // _rbcrecsize)
501 len(self._rbcrevs) // _rbcrecsize)
501 f = repo.vfs.open(_rbcrevs, 'ab')
502 f = repo.vfs.open(_rbcrevs, 'ab')
502 if f.tell() != start:
503 if f.tell() != start:
503 repo.ui.debug("truncating %s to %s\n" % (_rbcrevs, start))
504 repo.ui.debug("truncating %s to %s\n" % (_rbcrevs, start))
504 f.seek(start)
505 f.seek(start)
505 if f.tell() != start:
506 if f.tell() != start:
506 start = 0
507 start = 0
507 f.seek(start)
508 f.seek(start)
508 f.truncate()
509 f.truncate()
509 end = revs * _rbcrecsize
510 end = revs * _rbcrecsize
510 f.write(self._rbcrevs[start:end])
511 f.write(self._rbcrevs[start:end])
511 f.close()
512 f.close()
512 self._rbcrevslen = revs
513 self._rbcrevslen = revs
513 except (IOError, OSError, error.Abort, error.LockError) as inst:
514 except (IOError, OSError, error.Abort, error.LockError) as inst:
514 repo.ui.debug("couldn't write revision branch cache%s: %s\n"
515 repo.ui.debug("couldn't write revision branch cache%s: %s\n"
515 % (step, inst))
516 % (step, inst))
516 finally:
517 finally:
517 if wlock is not None:
518 if wlock is not None:
518 wlock.release()
519 wlock.release()
General Comments 0
You need to be logged in to leave comments. Login now