Mercurial > public > mercurial-scm > hg
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, []) |