##// END OF EJS Templates
filectx: add rename-aware ancestor algorithm...
Matt Mackall -
r3126:4bf2e895 default
parent child Browse files
Show More
@@ -1,147 +1,254 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from demandload import demandload
10 demandload(globals(), "heapq")
9
11
10 class changectx(object):
12 class changectx(object):
11 """A changecontext object makes access to data related to a particular
13 """A changecontext object makes access to data related to a particular
12 changeset convenient."""
14 changeset convenient."""
13 def __init__(self, repo, changeid):
15 def __init__(self, repo, changeid):
14 """changeid is a revision number, node, or tag"""
16 """changeid is a revision number, node, or tag"""
15 self._repo = repo
17 self._repo = repo
16
18
17 self._node = self._repo.lookup(changeid)
19 self._node = self._repo.lookup(changeid)
18 self._rev = self._repo.changelog.rev(self._node)
20 self._rev = self._repo.changelog.rev(self._node)
19
21
20 def changeset(self):
22 def changeset(self):
21 try:
23 try:
22 return self._changeset
24 return self._changeset
23 except AttributeError:
25 except AttributeError:
24 self._changeset = self._repo.changelog.read(self.node())
26 self._changeset = self._repo.changelog.read(self.node())
25 return self._changeset
27 return self._changeset
26
28
27 def manifest(self):
29 def manifest(self):
28 try:
30 try:
29 return self._manifest
31 return self._manifest
30 except AttributeError:
32 except AttributeError:
31 self._manifest = self._repo.manifest.read(self.changeset()[0])
33 self._manifest = self._repo.manifest.read(self.changeset()[0])
32 return self._manifest
34 return self._manifest
33
35
34 def rev(self): return self._rev
36 def rev(self): return self._rev
35 def node(self): return self._node
37 def node(self): return self._node
36 def user(self): return self.changeset()[1]
38 def user(self): return self.changeset()[1]
37 def date(self): return self.changeset()[2]
39 def date(self): return self.changeset()[2]
38 def files(self): return self.changeset()[3]
40 def files(self): return self.changeset()[3]
39 def description(self): return self.changeset()[4]
41 def description(self): return self.changeset()[4]
40
42
41 def parents(self):
43 def parents(self):
42 """return contexts for each parent changeset"""
44 """return contexts for each parent changeset"""
43 p = self._repo.changelog.parents(self._node)
45 p = self._repo.changelog.parents(self._node)
44 return [ changectx(self._repo, x) for x in p ]
46 return [ changectx(self._repo, x) for x in p ]
45
47
46 def children(self):
48 def children(self):
47 """return contexts for each child changeset"""
49 """return contexts for each child changeset"""
48 c = self._repo.changelog.children(self._node)
50 c = self._repo.changelog.children(self._node)
49 return [ changectx(self._repo, x) for x in c ]
51 return [ changectx(self._repo, x) for x in c ]
50
52
51 def filenode(self, path):
53 def filenode(self, path):
52 node, flag = self._repo.manifest.find(self.changeset()[0], path)
54 node, flag = self._repo.manifest.find(self.changeset()[0], path)
53 return node
55 return node
54
56
55 def filectx(self, path, fileid=None):
57 def filectx(self, path, fileid=None):
56 """get a file context from this changeset"""
58 """get a file context from this changeset"""
57 if fileid is None:
59 if fileid is None:
58 fileid = self.filenode(path)
60 fileid = self.filenode(path)
59 return filectx(self._repo, path, fileid=fileid)
61 return filectx(self._repo, path, fileid=fileid)
60
62
61 def filectxs(self):
63 def filectxs(self):
62 """generate a file context for each file in this changeset's
64 """generate a file context for each file in this changeset's
63 manifest"""
65 manifest"""
64 mf = self.manifest()
66 mf = self.manifest()
65 m = mf.keys()
67 m = mf.keys()
66 m.sort()
68 m.sort()
67 for f in m:
69 for f in m:
68 yield self.filectx(f, fileid=mf[f])
70 yield self.filectx(f, fileid=mf[f])
69
71
70 def ancestor(self, c2):
72 def ancestor(self, c2):
71 """
73 """
72 return the ancestor context of self and c2
74 return the ancestor context of self and c2
73 """
75 """
74 n = self._repo.changelog.ancestor(self._node, c2._node)
76 n = self._repo.changelog.ancestor(self._node, c2._node)
75 return changectx(self._repo, n)
77 return changectx(self._repo, n)
76
78
77 class filectx(object):
79 class filectx(object):
78 """A filecontext object makes access to data related to a particular
80 """A filecontext object makes access to data related to a particular
79 filerevision convenient."""
81 filerevision convenient."""
80 def __init__(self, repo, path, changeid=None, fileid=None, filelog=None):
82 def __init__(self, repo, path, changeid=None, fileid=None, filelog=None):
81 """changeid can be a changeset revision, node, or tag.
83 """changeid can be a changeset revision, node, or tag.
82 fileid can be a file revision or node."""
84 fileid can be a file revision or node."""
83 self._repo = repo
85 self._repo = repo
84 self._path = path
86 self._path = path
85
87
86 assert changeid or fileid
88 assert changeid or fileid
87
89
88 if filelog:
90 if filelog:
89 self._filelog = filelog
91 self._filelog = filelog
90 else:
92 else:
91 self._filelog = self._repo.file(self._path)
93 self._filelog = self._repo.file(self._path)
92
94
93 if not fileid:
95 if not fileid:
94 # if given a changeset id, go ahead and look up the file
96 # if given a changeset id, go ahead and look up the file
95 self._changeid = changeid
97 self._changeid = changeid
96 self._changectx = self.changectx()
98 self._changectx = self.changectx()
97 self._filenode = self._changectx.filenode(self._path)
99 self._filenode = self._changectx.filenode(self._path)
98 else:
100 else:
99 # else delay changectx creation
101 # else delay changectx creation
100 self._filenode = self._filelog.lookup(fileid)
102 self._filenode = self._filelog.lookup(fileid)
101 self._changeid = self._filelog.linkrev(self._filenode)
103 self._changeid = self._filelog.linkrev(self._filenode)
102 self._filerev = self._filelog.rev(self._filenode)
104 self._filerev = self._filelog.rev(self._filenode)
103
105
104 def changectx(self):
106 def changectx(self):
105 try:
107 try:
106 return self._changectx
108 return self._changectx
107 except AttributeError:
109 except AttributeError:
108 self._changectx = changectx(self._repo, self._changeid)
110 self._changectx = changectx(self._repo, self._changeid)
109 return self._changectx
111 return self._changectx
110
112
111 def filerev(self): return self._filerev
113 def filerev(self): return self._filerev
112 def filenode(self): return self._filenode
114 def filenode(self): return self._filenode
113 def filelog(self): return self._filelog
115 def filelog(self): return self._filelog
114
116
115 def rev(self): return self.changectx().rev()
117 def rev(self): return self.changectx().rev()
116 def node(self): return self.changectx().node()
118 def node(self): return self.changectx().node()
117 def user(self): return self.changectx().user()
119 def user(self): return self.changectx().user()
118 def date(self): return self.changectx().date()
120 def date(self): return self.changectx().date()
119 def files(self): return self.changectx().files()
121 def files(self): return self.changectx().files()
120 def description(self): return self.changectx().description()
122 def description(self): return self.changectx().description()
121 def manifest(self): return self.changectx().manifest()
123 def manifest(self): return self.changectx().manifest()
122
124
123 def data(self): return self._filelog.read(self._filenode)
125 def data(self): return self._filelog.read(self._filenode)
124 def renamed(self): return self._filelog.renamed(self._filenode)
126 def renamed(self): return self._filelog.renamed(self._filenode)
125 def path(self): return self._path
127 def path(self): return self._path
126
128
127 def parents(self):
129 def parents(self):
128 p = self._path
130 p = self._path
129 fl = self._filelog
131 fl = self._filelog
130 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
132 pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
131
133
132 r = self.renamed()
134 r = self.renamed()
133 if r:
135 if r:
134 pl[0] = (r[0], r[1], None)
136 pl[0] = (r[0], r[1], None)
135
137
136 return [ filectx(self._repo, p, fileid=n, filelog=l)
138 return [ filectx(self._repo, p, fileid=n, filelog=l)
137 for p,n,l in pl if n != nullid ]
139 for p,n,l in pl if n != nullid ]
138
140
139 def children(self):
141 def children(self):
140 # hard for renames
142 # hard for renames
141 c = self._filelog.children(self._filenode)
143 c = self._filelog.children(self._filenode)
142 return [ filectx(self._repo, self._path, fileid=x,
144 return [ filectx(self._repo, self._path, fileid=x,
143 filelog=self._filelog) for x in c ]
145 filelog=self._filelog) for x in c ]
144
146
145 def annotate(self):
147 def annotate(self):
146 return self._filelog.annotate(self._filenode)
148 return self._filelog.annotate(self._filenode)
147
149
150 def ancestor(self, fc2):
151 """
152 find the common ancestor file context, if any, of self, and fc2
153 """
154
155 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
156
157 if a == b:
158 return self
159
160 if a[0] == b[0]:
161 n = self._filelog.ancestor(a[1], b[1])
162 if n != nullid:
163 return filectx(self._repo, self._path,
164 fileid=n, filelog=self._filelog)
165
166 # build a graph of all ancestors, crossing renames
167 ag = {}
168 fv = [a, b]
169 flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
170
171 while fv:
172 f,n = fv.pop()
173 try:
174 fl = flcache[f]
175 except KeyError:
176 flcache[f] = self._repo.file(f)
177 fl = flcache[f]
178 v = [n]
179 while v:
180 n = v.pop()
181 c = (f, n)
182 if c in ag:
183 continue
184
185 pl = [ n for n in fl.parents(n) if n != nullid ]
186 v += pl
187 pl = [ (f, n) for n in pl ]
188 re = fl.renamed(n)
189 if re:
190 pl.append(re)
191 if re not in ag:
192 fv.append(re)
193 ag[c] = pl
194
195 dist = {}
196 def depth(node):
197 try:
198 return dist[node]
199 except KeyError:
200 pl = ag[node]
201 if not pl:
202 dist[node] = 0
203 else:
204 dist[node] = max([depth(p) for p in pl]) + 1
205 return dist[node]
206
207 # traverse ancestors in order of decreasing distance from root
208 def ancestors(vertex):
209 h = [(-depth(vertex), vertex)]
210 seen = {}
211 while h:
212 d, v = heapq.heappop(h)
213 if v not in seen:
214 seen[v] = 1
215 yield (-d, v)
216 for p in ag[v]:
217 heapq.heappush(h, (-depth(p), p))
218
219 def generations(vertex):
220 sg, s = None, {}
221 for g,v in ancestors(vertex):
222 if g != sg:
223 if sg:
224 yield sg, s
225 sg, s = g, {v:1}
226 else:
227 s[v] = 1
228 yield sg, s
229
230 x = generations(a)
231 y = generations(b)
232 gx = x.next()
233 gy = y.next()
234
235 # increment each ancestor list until it is closer to root than
236 # the other, or they match
237 try:
238 while 1:
239 if gx[0] == gy[0]:
240 # find the intersection
241 i = [ n for n in gx[1] if n in gy[1] ]
242 if i:
243 fp,fn = i[0]
244 fl = flcache[fp]
245 return filectx(self._repo, fp, fileid=fn, filelog=fl)
246 else:
247 gy = y.next()
248 gx = x.next()
249 elif gx[0] < gy[0]:
250 gy = y.next()
251 else:
252 gx = x.next()
253 except StopIteration:
254 return None
General Comments 0
You need to be logged in to leave comments. Login now