comparison mercurial/branchmap.py @ 20185:7d4219512823

branchmap: cache open/closed branch head information This lets us determine the open/closed state of a branch without reading from the changelog (which can be costly over NFS and/or with many branches).
author Brodie Rao <brodie@sf.io>
date Mon, 16 Sep 2013 01:08:29 -0700
parents b9515fb9e72a
children f5b461a4bc55
comparison
equal deleted inserted replaced
20184:a14d93b2fb1b 20185:7d4219512823
9 import encoding 9 import encoding
10 import util 10 import util
11 11
12 def _filename(repo): 12 def _filename(repo):
13 """name of a branchcache file for a given repo or repoview""" 13 """name of a branchcache file for a given repo or repoview"""
14 filename = "cache/branchheads" 14 filename = "cache/branch2"
15 if repo.filtername: 15 if repo.filtername:
16 filename = '%s-%s' % (filename, repo.filtername) 16 filename = '%s-%s' % (filename, repo.filtername)
17 return filename 17 return filename
18 18
19 def read(repo): 19 def read(repo):
37 # invalidate the cache 37 # invalidate the cache
38 raise ValueError('tip differs') 38 raise ValueError('tip differs')
39 for l in lines: 39 for l in lines:
40 if not l: 40 if not l:
41 continue 41 continue
42 node, label = l.split(" ", 1) 42 node, state, label = l.split(" ", 2)
43 if state not in 'oc':
44 raise ValueError('invalid branch state')
43 label = encoding.tolocal(label.strip()) 45 label = encoding.tolocal(label.strip())
44 if not node in repo: 46 if not node in repo:
45 raise ValueError('node %s does not exist' % node) 47 raise ValueError('node %s does not exist' % node)
46 partial.setdefault(label, []).append(bin(node)) 48 node = bin(node)
49 partial.setdefault(label, []).append(node)
50 if state == 'c':
51 partial._closednodes.add(node)
47 except KeyboardInterrupt: 52 except KeyboardInterrupt:
48 raise 53 raise
49 except Exception, inst: 54 except Exception, inst:
50 if repo.ui.debugflag: 55 if repo.ui.debugflag:
51 msg = 'invalid branchheads cache' 56 msg = 'invalid branchheads cache'
100 branch heads of a repo. 105 branch heads of a repo.
101 106
102 The cache is serialized on disk in the following format: 107 The cache is serialized on disk in the following format:
103 108
104 <tip hex node> <tip rev number> [optional filtered repo hex hash] 109 <tip hex node> <tip rev number> [optional filtered repo hex hash]
105 <branch head hex node> <branch name> 110 <branch head hex node> <open/closed state> <branch name>
106 <branch head hex node> <branch name> 111 <branch head hex node> <open/closed state> <branch name>
107 ... 112 ...
108 113
109 The first line is used to check if the cache is still valid. If the 114 The first line is used to check if the cache is still valid. If the
110 branch cache is for a filtered repo view, an optional third hash is 115 branch cache is for a filtered repo view, an optional third hash is
111 included that hashes the hashes of all filtered revisions. 116 included that hashes the hashes of all filtered revisions.
117
118 The open/closed state is represented by a single letter 'o' or 'c'.
119 This field can be used to avoid changelog reads when determining if a
120 branch head closes a branch or not.
112 """ 121 """
113 122
114 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev, 123 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
115 filteredhash=None): 124 filteredhash=None, closednodes=None):
116 super(branchcache, self).__init__(entries) 125 super(branchcache, self).__init__(entries)
117 self.tipnode = tipnode 126 self.tipnode = tipnode
118 self.tiprev = tiprev 127 self.tiprev = tiprev
119 self.filteredhash = filteredhash 128 self.filteredhash = filteredhash
129 # closednodes is a set of nodes that close their branch. If the branch
130 # cache has been updated, it may contain nodes that are no longer
131 # heads.
132 if closednodes is None:
133 self._closednodes = set()
134 else:
135 self._closednodes = closednodes
120 136
121 def _hashfiltered(self, repo): 137 def _hashfiltered(self, repo):
122 """build hash of revision filtered in the current cache 138 """build hash of revision filtered in the current cache
123 139
124 Tracking tipnode and tiprev is not enough to ensure validity of the 140 Tracking tipnode and tiprev is not enough to ensure validity of the
150 except IndexError: 166 except IndexError:
151 return False 167 return False
152 168
153 def copy(self): 169 def copy(self):
154 """return an deep copy of the branchcache object""" 170 """return an deep copy of the branchcache object"""
155 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash) 171 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash,
172 self._closednodes)
156 173
157 def write(self, repo): 174 def write(self, repo):
158 try: 175 try:
159 f = repo.opener(_filename(repo), "w", atomictemp=True) 176 f = repo.opener(_filename(repo), "w", atomictemp=True)
160 cachekey = [hex(self.tipnode), str(self.tiprev)] 177 cachekey = [hex(self.tipnode), str(self.tiprev)]
161 if self.filteredhash is not None: 178 if self.filteredhash is not None:
162 cachekey.append(hex(self.filteredhash)) 179 cachekey.append(hex(self.filteredhash))
163 f.write(" ".join(cachekey) + '\n') 180 f.write(" ".join(cachekey) + '\n')
164 for label, nodes in sorted(self.iteritems()): 181 for label, nodes in sorted(self.iteritems()):
165 for node in nodes: 182 for node in nodes:
166 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label))) 183 if node in self._closednodes:
184 state = 'c'
185 else:
186 state = 'o'
187 f.write("%s %s %s\n" % (hex(node), state,
188 encoding.fromlocal(label)))
167 f.close() 189 f.close()
168 except (IOError, OSError, util.Abort): 190 except (IOError, OSError, util.Abort):
169 # Abort may be raise by read only opener 191 # Abort may be raise by read only opener
170 pass 192 pass
171 193
175 heads missing, this function updates self to be correct. 197 heads missing, this function updates self to be correct.
176 """ 198 """
177 cl = repo.changelog 199 cl = repo.changelog
178 # collect new branch entries 200 # collect new branch entries
179 newbranches = {} 201 newbranches = {}
180 getbranch = cl.branch 202 getbranchinfo = cl.branchinfo
181 for r in revgen: 203 for r in revgen:
182 newbranches.setdefault(getbranch(r), []).append(cl.node(r)) 204 branch, closesbranch = getbranchinfo(r)
205 node = cl.node(r)
206 newbranches.setdefault(branch, []).append(node)
207 if closesbranch:
208 self._closednodes.add(node)
183 # if older branchheads are reachable from new ones, they aren't 209 # if older branchheads are reachable from new ones, they aren't
184 # really branchheads. Note checking parents is insufficient: 210 # really branchheads. Note checking parents is insufficient:
185 # 1 (branch a) -> 2 (branch b) -> 3 (branch a) 211 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
186 for branch, newnodes in newbranches.iteritems(): 212 for branch, newnodes in newbranches.iteritems():
187 bheads = self.setdefault(branch, []) 213 bheads = self.setdefault(branch, [])