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