##// END OF EJS Templates
branchmap: stop looking for stripped branch...
Pierre-Yves David -
r19839:a32ef044 default
parent child Browse files
Show More
@@ -1,221 +1,210 b''
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import bin, hex, nullid, nullrev
8 from node import bin, hex, nullid, nullrev
9 import encoding
9 import encoding
10 import util, repoview
10 import util, repoview
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/branchheads"
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):
20 try:
20 try:
21 f = repo.opener(_filename(repo))
21 f = repo.opener(_filename(repo))
22 lines = f.read().split('\n')
22 lines = f.read().split('\n')
23 f.close()
23 f.close()
24 except (IOError, OSError):
24 except (IOError, OSError):
25 return None
25 return None
26
26
27 try:
27 try:
28 cachekey = lines.pop(0).split(" ", 2)
28 cachekey = lines.pop(0).split(" ", 2)
29 last, lrev = cachekey[:2]
29 last, lrev = cachekey[:2]
30 last, lrev = bin(last), int(lrev)
30 last, lrev = bin(last), int(lrev)
31 filteredhash = None
31 filteredhash = None
32 if len(cachekey) > 2:
32 if len(cachekey) > 2:
33 filteredhash = bin(cachekey[2])
33 filteredhash = bin(cachekey[2])
34 partial = branchcache(tipnode=last, tiprev=lrev,
34 partial = branchcache(tipnode=last, tiprev=lrev,
35 filteredhash=filteredhash)
35 filteredhash=filteredhash)
36 if not partial.validfor(repo):
36 if not partial.validfor(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, label = l.split(" ", 1)
43 label = encoding.tolocal(label.strip())
43 label = encoding.tolocal(label.strip())
44 if not node in repo:
44 if not node in repo:
45 raise ValueError('node %s does not exist' % node)
45 raise ValueError('node %s does not exist' % node)
46 partial.setdefault(label, []).append(bin(node))
46 partial.setdefault(label, []).append(bin(node))
47 except KeyboardInterrupt:
47 except KeyboardInterrupt:
48 raise
48 raise
49 except Exception, inst:
49 except Exception, inst:
50 if repo.ui.debugflag:
50 if repo.ui.debugflag:
51 msg = 'invalid branchheads cache'
51 msg = 'invalid branchheads cache'
52 if repo.filtername is not None:
52 if repo.filtername is not None:
53 msg += ' (%s)' % repo.filtername
53 msg += ' (%s)' % repo.filtername
54 msg += ': %s\n'
54 msg += ': %s\n'
55 repo.ui.warn(msg % inst)
55 repo.ui.warn(msg % inst)
56 partial = None
56 partial = None
57 return partial
57 return partial
58
58
59
59
60
60
61 def updatecache(repo):
61 def updatecache(repo):
62 cl = repo.changelog
62 cl = repo.changelog
63 filtername = repo.filtername
63 filtername = repo.filtername
64 partial = repo._branchcaches.get(filtername)
64 partial = repo._branchcaches.get(filtername)
65
65
66 revs = []
66 revs = []
67 if partial is None or not partial.validfor(repo):
67 if partial is None or not partial.validfor(repo):
68 partial = read(repo)
68 partial = read(repo)
69 if partial is None:
69 if partial is None:
70 subsetname = repoview.subsettable.get(filtername)
70 subsetname = repoview.subsettable.get(filtername)
71 if subsetname is None:
71 if subsetname is None:
72 partial = branchcache()
72 partial = branchcache()
73 else:
73 else:
74 subset = repo.filtered(subsetname)
74 subset = repo.filtered(subsetname)
75 partial = subset.branchmap().copy()
75 partial = subset.branchmap().copy()
76 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
76 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
77 revs.extend(r for r in extrarevs if r <= partial.tiprev)
77 revs.extend(r for r in extrarevs if r <= partial.tiprev)
78 revs.extend(cl.revs(start=partial.tiprev + 1))
78 revs.extend(cl.revs(start=partial.tiprev + 1))
79 if revs:
79 if revs:
80 partial.update(repo, revs)
80 partial.update(repo, revs)
81 partial.write(repo)
81 partial.write(repo)
82 assert partial.validfor(repo), filtername
82 assert partial.validfor(repo), filtername
83 repo._branchcaches[repo.filtername] = partial
83 repo._branchcaches[repo.filtername] = partial
84
84
85 class branchcache(dict):
85 class branchcache(dict):
86 """A dict like object that hold branches heads cache"""
86 """A dict like object that hold branches heads cache"""
87
87
88 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
88 def __init__(self, entries=(), tipnode=nullid, tiprev=nullrev,
89 filteredhash=None):
89 filteredhash=None):
90 super(branchcache, self).__init__(entries)
90 super(branchcache, self).__init__(entries)
91 self.tipnode = tipnode
91 self.tipnode = tipnode
92 self.tiprev = tiprev
92 self.tiprev = tiprev
93 self.filteredhash = filteredhash
93 self.filteredhash = filteredhash
94
94
95 def _hashfiltered(self, repo):
95 def _hashfiltered(self, repo):
96 """build hash of revision filtered in the current cache
96 """build hash of revision filtered in the current cache
97
97
98 Tracking tipnode and tiprev is not enough to ensure validity of the
98 Tracking tipnode and tiprev is not enough to ensure validity of the
99 cache as they do not help to distinct cache that ignored various
99 cache as they do not help to distinct cache that ignored various
100 revision bellow tiprev.
100 revision bellow tiprev.
101
101
102 To detect such difference, we build a cache of all ignored revisions.
102 To detect such difference, we build a cache of all ignored revisions.
103 """
103 """
104 cl = repo.changelog
104 cl = repo.changelog
105 if not cl.filteredrevs:
105 if not cl.filteredrevs:
106 return None
106 return None
107 key = None
107 key = None
108 revs = sorted(r for r in cl.filteredrevs if r <= self.tiprev)
108 revs = sorted(r for r in cl.filteredrevs if r <= self.tiprev)
109 if revs:
109 if revs:
110 s = util.sha1()
110 s = util.sha1()
111 for rev in revs:
111 for rev in revs:
112 s.update('%s;' % rev)
112 s.update('%s;' % rev)
113 key = s.digest()
113 key = s.digest()
114 return key
114 return key
115
115
116 def validfor(self, repo):
116 def validfor(self, repo):
117 """Is the cache content valid regarding a repo
117 """Is the cache content valid regarding a repo
118
118
119 - False when cached tipnode is unknown or if we detect a strip.
119 - False when cached tipnode is unknown or if we detect a strip.
120 - True when cache is up to date or a subset of current repo."""
120 - True when cache is up to date or a subset of current repo."""
121 try:
121 try:
122 return ((self.tipnode == repo.changelog.node(self.tiprev))
122 return ((self.tipnode == repo.changelog.node(self.tiprev))
123 and (self.filteredhash == self._hashfiltered(repo)))
123 and (self.filteredhash == self._hashfiltered(repo)))
124 except IndexError:
124 except IndexError:
125 return False
125 return False
126
126
127 def copy(self):
127 def copy(self):
128 """return an deep copy of the branchcache object"""
128 """return an deep copy of the branchcache object"""
129 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash)
129 return branchcache(self, self.tipnode, self.tiprev, self.filteredhash)
130
130
131 def write(self, repo):
131 def write(self, repo):
132 try:
132 try:
133 f = repo.opener(_filename(repo), "w", atomictemp=True)
133 f = repo.opener(_filename(repo), "w", atomictemp=True)
134 cachekey = [hex(self.tipnode), str(self.tiprev)]
134 cachekey = [hex(self.tipnode), str(self.tiprev)]
135 if self.filteredhash is not None:
135 if self.filteredhash is not None:
136 cachekey.append(hex(self.filteredhash))
136 cachekey.append(hex(self.filteredhash))
137 f.write(" ".join(cachekey) + '\n')
137 f.write(" ".join(cachekey) + '\n')
138 for label, nodes in sorted(self.iteritems()):
138 for label, nodes in sorted(self.iteritems()):
139 for node in nodes:
139 for node in nodes:
140 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
140 f.write("%s %s\n" % (hex(node), encoding.fromlocal(label)))
141 f.close()
141 f.close()
142 except (IOError, OSError, util.Abort):
142 except (IOError, OSError, util.Abort):
143 # Abort may be raise by read only opener
143 # Abort may be raise by read only opener
144 pass
144 pass
145
145
146 def update(self, repo, revgen):
146 def update(self, repo, revgen):
147 """Given a branchhead cache, self, that may have extra nodes or be
147 """Given a branchhead cache, self, that may have extra nodes or be
148 missing heads, and a generator of nodes that are at least a superset of
148 missing heads, and a generator of nodes that are at least a superset of
149 heads missing, this function updates self to be correct.
149 heads missing, this function updates self to be correct.
150 """
150 """
151 cl = repo.changelog
151 cl = repo.changelog
152 # collect new branch entries
152 # collect new branch entries
153 newbranches = {}
153 newbranches = {}
154 getbranch = cl.branch
154 getbranch = cl.branch
155 for r in revgen:
155 for r in revgen:
156 newbranches.setdefault(getbranch(r), []).append(cl.node(r))
156 newbranches.setdefault(getbranch(r), []).append(cl.node(r))
157 # if older branchheads are reachable from new ones, they aren't
157 # if older branchheads are reachable from new ones, they aren't
158 # really branchheads. Note checking parents is insufficient:
158 # really branchheads. Note checking parents is insufficient:
159 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
159 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
160 for branch, newnodes in newbranches.iteritems():
160 for branch, newnodes in newbranches.iteritems():
161 bheads = self.setdefault(branch, [])
161 bheads = self.setdefault(branch, [])
162 # Remove candidate heads that no longer are in the repo (e.g., as
162 # Remove candidate heads that no longer are in the repo (e.g., as
163 # the result of a strip that just happened). Avoid using 'node in
163 # the result of a strip that just happened). Avoid using 'node in
164 # self' here because that dives down into branchcache code somewhat
164 # self' here because that dives down into branchcache code somewhat
165 # recursively.
165 # recursively.
166 bheadrevs = [cl.rev(node) for node in bheads
166 bheadrevs = [cl.rev(node) for node in bheads
167 if cl.hasnode(node)]
167 if cl.hasnode(node)]
168 newheadrevs = [cl.rev(node) for node in newnodes
168 newheadrevs = [cl.rev(node) for node in newnodes
169 if cl.hasnode(node)]
169 if cl.hasnode(node)]
170 ctxisnew = bheadrevs and min(newheadrevs) > max(bheadrevs)
170 ctxisnew = bheadrevs and min(newheadrevs) > max(bheadrevs)
171 # Remove duplicates - nodes that are in newheadrevs and are already
171 # Remove duplicates - nodes that are in newheadrevs and are already
172 # in bheadrevs. This can happen if you strip a node whose parent
172 # in bheadrevs. This can happen if you strip a node whose parent
173 # was already a head (because they're on different branches).
173 # was already a head (because they're on different branches).
174 bheadrevs = sorted(set(bheadrevs).union(newheadrevs))
174 bheadrevs = sorted(set(bheadrevs).union(newheadrevs))
175
175
176 # Starting from tip means fewer passes over reachable. If we know
176 # Starting from tip means fewer passes over reachable. If we know
177 # the new candidates are not ancestors of existing heads, we don't
177 # the new candidates are not ancestors of existing heads, we don't
178 # have to examine ancestors of existing heads
178 # have to examine ancestors of existing heads
179 if ctxisnew:
179 if ctxisnew:
180 iterrevs = sorted(newheadrevs)
180 iterrevs = sorted(newheadrevs)
181 else:
181 else:
182 iterrevs = list(bheadrevs)
182 iterrevs = list(bheadrevs)
183
183
184 # This loop prunes out two kinds of heads - heads that are
184 # This loop prunes out two kinds of heads - heads that are
185 # superseded by a head in newheadrevs, and newheadrevs that are not
185 # superseded by a head in newheadrevs, and newheadrevs that are not
186 # heads because an existing head is their descendant.
186 # heads because an existing head is their descendant.
187 while iterrevs:
187 while iterrevs:
188 latest = iterrevs.pop()
188 latest = iterrevs.pop()
189 if latest not in bheadrevs:
189 if latest not in bheadrevs:
190 continue
190 continue
191 ancestors = set(cl.ancestors([latest],
191 ancestors = set(cl.ancestors([latest],
192 bheadrevs[0]))
192 bheadrevs[0]))
193 if ancestors:
193 if ancestors:
194 bheadrevs = [b for b in bheadrevs if b not in ancestors]
194 bheadrevs = [b for b in bheadrevs if b not in ancestors]
195 self[branch] = [cl.node(rev) for rev in bheadrevs]
195 self[branch] = [cl.node(rev) for rev in bheadrevs]
196 tiprev = max(bheadrevs)
196 tiprev = max(bheadrevs)
197 if tiprev > self.tiprev:
197 if tiprev > self.tiprev:
198 self.tipnode = cl.node(tiprev)
198 self.tipnode = cl.node(tiprev)
199 self.tiprev = tiprev
199 self.tiprev = tiprev
200
200
201 # There may be branches that cease to exist when the last commit in the
202 # branch was stripped. This code filters them out. Note that the
203 # branch that ceased to exist may not be in newbranches because
204 # newbranches is the set of candidate heads, which when you strip the
205 # last commit in a branch will be the parent branch.
206 for branch in self.keys():
207 nodes = [head for head in self[branch]
208 if cl.hasnode(head)]
209 if not nodes:
210 del self[branch]
211
212 if not self.validfor(repo):
201 if not self.validfor(repo):
213 # cache key are not valid anymore
202 # cache key are not valid anymore
214 self.tipnode = nullid
203 self.tipnode = nullid
215 self.tiprev = nullrev
204 self.tiprev = nullrev
216 for heads in self.values():
205 for heads in self.values():
217 tiprev = max(cl.rev(node) for node in heads)
206 tiprev = max(cl.rev(node) for node in heads)
218 if tiprev > self.tiprev:
207 if tiprev > self.tiprev:
219 self.tipnode = cl.node(tiprev)
208 self.tipnode = cl.node(tiprev)
220 self.tiprev = tiprev
209 self.tiprev = tiprev
221 self.filteredhash = self._hashfiltered(repo)
210 self.filteredhash = self._hashfiltered(repo)
General Comments 0
You need to be logged in to leave comments. Login now