##// END OF EJS Templates
branchmap: simplify update code...
Pierre-Yves David -
r20263:ea499675 default
parent child Browse files
Show More
@@ -1,281 +1,275 b''
1 1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import bin, hex, nullid, nullrev
9 9 import encoding
10 10 import util
11 11
12 12 def _filename(repo):
13 13 """name of a branchcache file for a given repo or repoview"""
14 14 filename = "cache/branch2"
15 15 if repo.filtername:
16 16 filename = '%s-%s' % (filename, repo.filtername)
17 17 return filename
18 18
19 19 def read(repo):
20 20 try:
21 21 f = repo.opener(_filename(repo))
22 22 lines = f.read().split('\n')
23 23 f.close()
24 24 except (IOError, OSError):
25 25 return None
26 26
27 27 try:
28 28 cachekey = lines.pop(0).split(" ", 2)
29 29 last, lrev = cachekey[:2]
30 30 last, lrev = bin(last), int(lrev)
31 31 filteredhash = None
32 32 if len(cachekey) > 2:
33 33 filteredhash = bin(cachekey[2])
34 34 partial = branchcache(tipnode=last, tiprev=lrev,
35 35 filteredhash=filteredhash)
36 36 if not partial.validfor(repo):
37 37 # invalidate the cache
38 38 raise ValueError('tip differs')
39 39 for l in lines:
40 40 if not l:
41 41 continue
42 42 node, state, label = l.split(" ", 2)
43 43 if state not in 'oc':
44 44 raise ValueError('invalid branch state')
45 45 label = encoding.tolocal(label.strip())
46 46 if not node in repo:
47 47 raise ValueError('node %s does not exist' % node)
48 48 node = bin(node)
49 49 partial.setdefault(label, []).append(node)
50 50 if state == 'c':
51 51 partial._closednodes.add(node)
52 52 except KeyboardInterrupt:
53 53 raise
54 54 except Exception, inst:
55 55 if repo.ui.debugflag:
56 56 msg = 'invalid branchheads cache'
57 57 if repo.filtername is not None:
58 58 msg += ' (%s)' % repo.filtername
59 59 msg += ': %s\n'
60 60 repo.ui.warn(msg % inst)
61 61 partial = None
62 62 return partial
63 63
64 64
65 65
66 66 ### Nearest subset relation
67 67 # Nearest subset of filter X is a filter Y so that:
68 68 # * Y is included in X,
69 69 # * X - Y is as small as possible.
70 70 # This create and ordering used for branchmap purpose.
71 71 # the ordering may be partial
72 72 subsettable = {None: 'visible',
73 73 'visible': 'served',
74 74 'served': 'immutable',
75 75 'immutable': 'base'}
76 76
77 77 def updatecache(repo):
78 78 cl = repo.changelog
79 79 filtername = repo.filtername
80 80 partial = repo._branchcaches.get(filtername)
81 81
82 82 revs = []
83 83 if partial is None or not partial.validfor(repo):
84 84 partial = read(repo)
85 85 if partial is None:
86 86 subsetname = subsettable.get(filtername)
87 87 if subsetname is None:
88 88 partial = branchcache()
89 89 else:
90 90 subset = repo.filtered(subsetname)
91 91 partial = subset.branchmap().copy()
92 92 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
93 93 revs.extend(r for r in extrarevs if r <= partial.tiprev)
94 94 revs.extend(cl.revs(start=partial.tiprev + 1))
95 95 if revs:
96 96 partial.update(repo, revs)
97 97 partial.write(repo)
98 98 assert partial.validfor(repo), filtername
99 99 repo._branchcaches[repo.filtername] = partial
100 100
101 101 class branchcache(dict):
102 102 """A dict like object that hold branches heads cache.
103 103
104 104 This cache is used to avoid costly computations to determine all the
105 105 branch heads of a repo.
106 106
107 107 The cache is serialized on disk in the following format:
108 108
109 109 <tip hex node> <tip rev number> [optional filtered repo hex hash]
110 110 <branch head hex node> <open/closed state> <branch name>
111 111 <branch head hex node> <open/closed state> <branch name>
112 112 ...
113 113
114 114 The first line is used to check if the cache is still valid. If the
115 115 branch cache is for a filtered repo view, an optional third hash is
116 116 included that hashes the hashes of all filtered revisions.
117 117
118 118 The open/closed state is represented by a single letter 'o' or 'c'.
119 119 This field can be used to avoid changelog reads when determining if a
120 120 branch head closes a branch or not.
121 121 """
122 122
123 123 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
124 124 filteredhash=None, closednodes=None):
125 125 super(branchcache, self).__init__(entries)
126 126 self.tipnode = tipnode
127 127 self.tiprev = tiprev
128 128 self.filteredhash = filteredhash
129 129 # closednodes is a set of nodes that close their branch. If the branch
130 130 # cache has been updated, it may contain nodes that are no longer
131 131 # heads.
132 132 if closednodes is None:
133 133 self._closednodes = set()
134 134 else:
135 135 self._closednodes = closednodes
136 136
137 137 def _hashfiltered(self, repo):
138 138 """build hash of revision filtered in the current cache
139 139
140 140 Tracking tipnode and tiprev is not enough to ensure validity of the
141 141 cache as they do not help to distinct cache that ignored various
142 142 revision bellow tiprev.
143 143
144 144 To detect such difference, we build a cache of all ignored revisions.
145 145 """
146 146 cl = repo.changelog
147 147 if not cl.filteredrevs:
148 148 return None
149 149 key = None
150 150 revs = sorted(r for r in cl.filteredrevs if r <= self.tiprev)
151 151 if revs:
152 152 s = util.sha1()
153 153 for rev in revs:
154 154 s.update('%s;' % rev)
155 155 key = s.digest()
156 156 return key
157 157
158 158 def validfor(self, repo):
159 159 """Is the cache content valid regarding a repo
160 160
161 161 - False when cached tipnode is unknown or if we detect a strip.
162 162 - True when cache is up to date or a subset of current repo."""
163 163 try:
164 164 return ((self.tipnode == repo.changelog.node(self.tiprev))
165 165 and (self.filteredhash == self._hashfiltered(repo)))
166 166 except IndexError:
167 167 return False
168 168
169 169 def _branchtip(self, heads):
170 170 '''Return tuple with last open head in heads and false,
171 171 otherwise return last closed head and true.'''
172 172 tip = heads[-1]
173 173 closed = True
174 174 for h in reversed(heads):
175 175 if h not in self._closednodes:
176 176 tip = h
177 177 closed = False
178 178 break
179 179 return tip, closed
180 180
181 181 def branchtip(self, branch):
182 182 '''Return the tipmost open head on branch head, otherwise return the
183 183 tipmost closed head on branch.
184 184 Raise KeyError for unknown branch.'''
185 185 return self._branchtip(self[branch])[0]
186 186
187 187 def branchheads(self, branch, closed=False):
188 188 heads = self[branch]
189 189 if not closed:
190 190 heads = [h for h in heads if h not in self._closednodes]
191 191 return heads
192 192
193 193 def iterbranches(self):
194 194 for bn, heads in self.iteritems():
195 195 yield (bn, heads) + self._branchtip(heads)
196 196
197 197 def copy(self):
198 198 """return an deep copy of the branchcache object"""
199 199 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash,
200 200 self._closednodes)
201 201
202 202 def write(self, repo):
203 203 try:
204 204 f = repo.opener(_filename(repo), "w", atomictemp=True)
205 205 cachekey = [hex(self.tipnode), str(self.tiprev)]
206 206 if self.filteredhash is not None:
207 207 cachekey.append(hex(self.filteredhash))
208 208 f.write(" ".join(cachekey) + '\n')
209 209 for label, nodes in sorted(self.iteritems()):
210 210 for node in nodes:
211 211 if node in self._closednodes:
212 212 state = 'c'
213 213 else:
214 214 state = 'o'
215 215 f.write("%s %s %s\n" % (hex(node), state,
216 216 encoding.fromlocal(label)))
217 217 f.close()
218 218 except (IOError, OSError, util.Abort):
219 219 # Abort may be raise by read only opener
220 220 pass
221 221
222 222 def update(self, repo, revgen):
223 223 """Given a branchhead cache, self, that may have extra nodes or be
224 missing heads, and a generator of nodes that are at least a superset of
224 missing heads, and a generator of nodes that are strictly a superset of
225 225 heads missing, this function updates self to be correct.
226 226 """
227 227 cl = repo.changelog
228 228 # collect new branch entries
229 229 newbranches = {}
230 230 getbranchinfo = cl.branchinfo
231 231 for r in revgen:
232 232 branch, closesbranch = getbranchinfo(r)
233 233 newbranches.setdefault(branch, []).append(r)
234 234 if closesbranch:
235 235 self._closednodes.add(cl.node(r))
236 236 # if older branchheads are reachable from new ones, they aren't
237 237 # really branchheads. Note checking parents is insufficient:
238 238 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
239 239 for branch, newheadrevs in newbranches.iteritems():
240 240 bheads = self.setdefault(branch, [])
241 241 bheadrevs = [cl.rev(node) for node in bheads]
242 ctxisnew = bheadrevs and min(newheadrevs) > max(bheadrevs)
243 # Remove duplicates - nodes that are in newheadrevs and are already
244 # in bheadrevs. This can happen if you strip a node whose parent
245 # was already a head (because they're on different branches).
246 bheadrevs = sorted(set(bheadrevs).union(newheadrevs))
247 242
248 # Starting from tip means fewer passes over reachable. If we know
249 # the new candidates are not ancestors of existing heads, we don't
250 # have to examine ancestors of existing heads
251 if ctxisnew:
252 iterrevs = sorted(newheadrevs)
253 else:
254 iterrevs = list(bheadrevs)
243 # This have been tested True on all internal usage of this function.
244 # run it again in case of doubt
245 # assert not (set(bheadrevs) & set(newheadrevs))
246 newheadrevs.sort()
247 bheadrevs.extend(newheadrevs)
248 bheadrevs.sort()
255 249
256 250 # This loop prunes out two kinds of heads - heads that are
257 251 # superseded by a head in newheadrevs, and newheadrevs that are not
258 252 # heads because an existing head is their descendant.
259 while iterrevs:
260 latest = iterrevs.pop()
253 while newheadrevs:
254 latest = newheadrevs.pop()
261 255 if latest not in bheadrevs:
262 256 continue
263 257 ancestors = set(cl.ancestors([latest], bheadrevs[0]))
264 258 if ancestors:
265 259 bheadrevs = [b for b in bheadrevs if b not in ancestors]
266 260 self[branch] = [cl.node(rev) for rev in bheadrevs]
267 tiprev = max(bheadrevs)
261 tiprev = bheadrevs[-1]
268 262 if tiprev > self.tiprev:
269 263 self.tipnode = cl.node(tiprev)
270 264 self.tiprev = tiprev
271 265
272 266 if not self.validfor(repo):
273 267 # cache key are not valid anymore
274 268 self.tipnode = nullid
275 269 self.tiprev = nullrev
276 270 for heads in self.values():
277 271 tiprev = max(cl.rev(node) for node in heads)
278 272 if tiprev > self.tiprev:
279 273 self.tipnode = cl.node(tiprev)
280 274 self.tiprev = tiprev
281 275 self.filteredhash = self._hashfiltered(repo)
1 NO CONTENT: modified file, binary diff hidden
General Comments 0
You need to be logged in to leave comments. Login now