Mercurial > evolve
comparison src/topic/topicmap.py @ 1890:e846b8f402d0
topicmap: write and read format from disc
To prevent too awful performance we allow writing and reading topicmap cache.
This is done with a lot of code duplication from core because core is not
extensible enough.
author | Pierre-Yves David <pierre-yves.david@fb.com> |
---|---|
date | Sun, 13 Mar 2016 23:44:04 +0000 |
parents | d49f75eab6a3 |
children |
comparison
equal
deleted
inserted
replaced
1889:d9b929bcc3ad | 1890:e846b8f402d0 |
---|---|
1 from mercurial import branchmap | 1 from mercurial import branchmap |
2 from mercurial import encoding | |
3 from mercurial import error | |
4 from mercurial import scmutil | |
5 from mercurial import util | |
6 from mercurial.node import hex, bin, nullid | |
2 | 7 |
3 def _filename(repo): | 8 def _filename(repo): |
4 """name of a branchcache file for a given repo or repoview""" | 9 """name of a branchcache file for a given repo or repoview""" |
5 filename = "cache/topicmap" | 10 filename = "cache/topicmap" |
6 if repo.filtername: | 11 if repo.filtername: |
7 filename = '%s-%s' % (filename, repo.filtername) | 12 filename = '%s-%s' % (filename, repo.filtername) |
8 return filename | 13 return filename |
9 | 14 |
10 oldbranchcache = branchmap.branchcache | 15 oldbranchcache = branchmap.branchcache |
11 | 16 |
17 def _phaseshash(repo, maxrev): | |
18 revs = set() | |
19 cl = repo.changelog | |
20 fr = cl.filteredrevs | |
21 nm = cl.nodemap | |
22 for roots in repo._phasecache.phaseroots[1:]: | |
23 for n in roots: | |
24 r = nm.get(n) | |
25 if r not in fr and r < maxrev: | |
26 revs.add(r) | |
27 key = nullid | |
28 revs = sorted(revs) | |
29 if revs: | |
30 s = util.sha1() | |
31 for rev in revs: | |
32 s.update('%s;' % rev) | |
33 key = s.digest() | |
34 return key | |
35 | |
12 class topiccache(oldbranchcache): | 36 class topiccache(oldbranchcache): |
13 | 37 |
14 def __init__(self, *args, **kwargs): | 38 def __init__(self, *args, **kwargs): |
15 otherbranchcache = branchmap.branchcache | 39 otherbranchcache = branchmap.branchcache |
16 try: | 40 try: |
17 # super() call may fail otherwise | 41 # super() call may fail otherwise |
18 branchmap.branchcache = oldbranchcache | 42 branchmap.branchcache = oldbranchcache |
19 return super(topiccache, self).__init__(*args, **kwargs) | 43 super(topiccache, self).__init__(*args, **kwargs) |
44 if self.filteredhash is None: | |
45 self.filteredhash = nullid | |
46 self.phaseshash = nullid | |
20 finally: | 47 finally: |
21 branchmap.branchcache = otherbranchcache | 48 branchmap.branchcache = otherbranchcache |
49 | |
50 def copy(self): | |
51 """return an deep copy of the branchcache object""" | |
52 new = topiccache(self, self.tipnode, self.tiprev, self.filteredhash, | |
53 self._closednodes) | |
54 if self.filteredhash is None: | |
55 self.filteredhash = nullid | |
56 new.phaseshash = self.phaseshash | |
57 return new | |
22 | 58 |
23 def branchtip(self, branch, topic=''): | 59 def branchtip(self, branch, topic=''): |
24 '''Return the tipmost open head on branch head, otherwise return the | 60 '''Return the tipmost open head on branch head, otherwise return the |
25 tipmost closed head on branch. | 61 tipmost closed head on branch. |
26 Raise KeyError for unknown branch.''' | 62 Raise KeyError for unknown branch.''' |
31 def branchheads(self, branch, closed=False, topic=''): | 67 def branchheads(self, branch, closed=False, topic=''): |
32 if topic: | 68 if topic: |
33 branch = '%s:%s' % (branch, topic) | 69 branch = '%s:%s' % (branch, topic) |
34 return super(topiccache, self).branchheads(branch, closed=closed) | 70 return super(topiccache, self).branchheads(branch, closed=closed) |
35 | 71 |
72 def validfor(self, repo): | |
73 """Is the cache content valid regarding a repo | |
74 | |
75 - False when cached tipnode is unknown or if we detect a strip. | |
76 - True when cache is up to date or a subset of current repo.""" | |
77 # This is copy paste of mercurial.branchmap.branchcache.validfor in | |
78 # 69077c65919d With a small changes to the cache key handling to | |
79 # include phase information that impact the topic cache. | |
80 # | |
81 # All code changes should be flagged on site. | |
82 try: | |
83 if (self.tipnode == repo.changelog.node(self.tiprev)): | |
84 fh = scmutil.filteredhash(repo, self.tiprev) | |
85 if fh is None: | |
86 fh = nullid | |
87 if ((self.filteredhash == fh) | |
88 and (self.phaseshash == _phaseshash(repo, self.tiprev))): | |
89 return True | |
90 return False | |
91 except IndexError: | |
92 return False | |
93 | |
36 def write(self, repo): | 94 def write(self, repo): |
37 # The cache key needs to take phase root in account because change to | 95 # This is copy paste of mercurial.branchmap.branchcache.write in |
38 # what is public affect topics. We can't write on disk until we have this. | 96 # 69077c65919d With a small changes to the cache key handling to |
39 return | 97 # include phase information that impact the topic cache. |
98 # | |
99 # All code changes should be flagged on site. | |
100 try: | |
101 f = repo.vfs(_filename(repo), "w", atomictemp=True) | |
102 cachekey = [hex(self.tipnode), str(self.tiprev)] | |
103 # [CHANGE] we need a hash in all cases | |
104 assert self.filteredhash is not None | |
105 cachekey.append(hex(self.filteredhash)) | |
106 cachekey.append(hex(self.phaseshash)) | |
107 f.write(" ".join(cachekey) + '\n') | |
108 nodecount = 0 | |
109 for label, nodes in sorted(self.iteritems()): | |
110 for node in nodes: | |
111 nodecount += 1 | |
112 if node in self._closednodes: | |
113 state = 'c' | |
114 else: | |
115 state = 'o' | |
116 f.write("%s %s %s\n" % (hex(node), state, | |
117 encoding.fromlocal(label))) | |
118 f.close() | |
119 repo.ui.log('branchcache', | |
120 'wrote %s branch cache with %d labels and %d nodes\n', | |
121 repo.filtername, len(self), nodecount) | |
122 except (IOError, OSError, error.Abort) as inst: | |
123 repo.ui.debug("couldn't write branch cache: %s\n" % inst) | |
124 # Abort may be raise by read only opener | |
125 pass | |
40 | 126 |
41 def update(self, repo, revgen): | 127 def update(self, repo, revgen): |
42 """Given a branchhead cache, self, that may have extra nodes or be | 128 """Given a branchhead cache, self, that may have extra nodes or be |
43 missing heads, and a generator of nodes that are strictly a superset of | 129 missing heads, and a generator of nodes that are strictly a superset of |
44 heads missing, this function updates self to be correct. | 130 heads missing, this function updates self to be correct. |
54 branch = info[0] | 140 branch = info[0] |
55 if topic: | 141 if topic: |
56 branch = '%s:%s' % (branch, topic) | 142 branch = '%s:%s' % (branch, topic) |
57 return (branch, info[1]) | 143 return (branch, info[1]) |
58 repo.revbranchcache().branchinfo = branchinfo | 144 repo.revbranchcache().branchinfo = branchinfo |
59 return super(topiccache, self).update(repo, revgen) | 145 super(topiccache, self).update(repo, revgen) |
146 if self.filteredhash is None: | |
147 self.filteredhash = nullid | |
148 self.phaseshash = _phaseshash(repo, self.tiprev) | |
60 finally: | 149 finally: |
61 repo.revbranchcache().branchinfo = oldgetbranchinfo | 150 repo.revbranchcache().branchinfo = oldgetbranchinfo |
151 | |
152 def readtopicmap(repo): | |
153 # This is copy paste of mercurial.branchmap.read in 69077c65919d | |
154 # With a small changes to the cache key handling to include phase | |
155 # information that impact the topic cache. | |
156 # | |
157 # All code changes should be flagged on site. | |
158 try: | |
159 f = repo.vfs(_filename(repo)) | |
160 lines = f.read().split('\n') | |
161 f.close() | |
162 except (IOError, OSError): | |
163 return None | |
164 | |
165 try: | |
166 cachekey = lines.pop(0).split(" ", 2) | |
167 last, lrev = cachekey[:2] | |
168 last, lrev = bin(last), int(lrev) | |
169 filteredhash = bin(cachekey[2]) # [CHANGE] unconditional filteredhash | |
170 partial = branchcache(tipnode=last, tiprev=lrev, | |
171 filteredhash=filteredhash) | |
172 partial.phaseshash = bin(cachekey[3]) # [CHANGE] read phaseshash | |
173 if not partial.validfor(repo): | |
174 # invalidate the cache | |
175 raise ValueError('tip differs') | |
176 cl = repo.changelog | |
177 for l in lines: | |
178 if not l: | |
179 continue | |
180 node, state, label = l.split(" ", 2) | |
181 if state not in 'oc': | |
182 raise ValueError('invalid branch state') | |
183 label = encoding.tolocal(label.strip()) | |
184 node = bin(node) | |
185 if not cl.hasnode(node): | |
186 raise ValueError('node %s does not exist' % hex(node)) | |
187 partial.setdefault(label, []).append(node) | |
188 if state == 'c': | |
189 partial._closednodes.add(node) | |
190 except KeyboardInterrupt: | |
191 raise | |
192 except Exception as inst: | |
193 if repo.ui.debugflag: | |
194 msg = 'invalid branchheads cache' | |
195 if repo.filtername is not None: | |
196 msg += ' (%s)' % repo.filtername | |
197 msg += ': %s\n' | |
198 repo.ui.debug(msg % inst) | |
199 partial = None | |
200 return partial |