|
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 |
|
7 |
|
8 def _filename(repo): |
|
9 """name of a branchcache file for a given repo or repoview""" |
|
10 filename = "cache/topicmap" |
|
11 if repo.filtername: |
|
12 filename = '%s-%s' % (filename, repo.filtername) |
|
13 return filename |
|
14 |
|
15 oldbranchcache = branchmap.branchcache |
|
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 |
|
36 class topiccache(oldbranchcache): |
|
37 |
|
38 def __init__(self, *args, **kwargs): |
|
39 otherbranchcache = branchmap.branchcache |
|
40 try: |
|
41 # super() call may fail otherwise |
|
42 branchmap.branchcache = oldbranchcache |
|
43 super(topiccache, self).__init__(*args, **kwargs) |
|
44 if self.filteredhash is None: |
|
45 self.filteredhash = nullid |
|
46 self.phaseshash = nullid |
|
47 finally: |
|
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 |
|
58 |
|
59 def branchtip(self, branch, topic=''): |
|
60 '''Return the tipmost open head on branch head, otherwise return the |
|
61 tipmost closed head on branch. |
|
62 Raise KeyError for unknown branch.''' |
|
63 if topic: |
|
64 branch = '%s:%s' % (branch, topic) |
|
65 return super(topiccache, self).branchtip(branch) |
|
66 |
|
67 def branchheads(self, branch, closed=False, topic=''): |
|
68 if topic: |
|
69 branch = '%s:%s' % (branch, topic) |
|
70 return super(topiccache, self).branchheads(branch, closed=closed) |
|
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 |
|
94 def write(self, repo): |
|
95 # This is copy paste of mercurial.branchmap.branchcache.write in |
|
96 # 69077c65919d With a small changes to the cache key handling to |
|
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 |
|
126 |
|
127 def update(self, repo, revgen): |
|
128 """Given a branchhead cache, self, that may have extra nodes or be |
|
129 missing heads, and a generator of nodes that are strictly a superset of |
|
130 heads missing, this function updates self to be correct. |
|
131 """ |
|
132 oldgetbranchinfo = repo.revbranchcache().branchinfo |
|
133 try: |
|
134 def branchinfo(r): |
|
135 info = oldgetbranchinfo(r) |
|
136 topic = '' |
|
137 ctx = repo[r] |
|
138 if ctx.mutable(): |
|
139 topic = ctx.topic() |
|
140 branch = info[0] |
|
141 if topic: |
|
142 branch = '%s:%s' % (branch, topic) |
|
143 return (branch, info[1]) |
|
144 repo.revbranchcache().branchinfo = branchinfo |
|
145 super(topiccache, self).update(repo, revgen) |
|
146 if self.filteredhash is None: |
|
147 self.filteredhash = nullid |
|
148 self.phaseshash = _phaseshash(repo, self.tiprev) |
|
149 finally: |
|
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 |