##// END OF EJS Templates
context: simplify changeid logic
Matt Mackall -
r6741:5918e2b7 default
parent child Browse files
Show More
@@ -1,758 +1,753 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, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 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 nullid, nullrev, short
8 from node import nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, revlog, util, os, errno
10 import ancestor, bdiff, revlog, util, os, errno
11
11
12 class changectx(object):
12 class changectx(object):
13 """A changecontext object makes access to data related to a particular
13 """A changecontext object makes access to data related to a particular
14 changeset convenient."""
14 changeset convenient."""
15 def __init__(self, repo, changeid=None):
15 def __init__(self, repo, changeid=''):
16 """changeid is a revision number, node, or tag"""
16 """changeid is a revision number, node, or tag"""
17 if changeid == '':
18 changeid = '.'
17 self._repo = repo
19 self._repo = repo
18
19 if not changeid and changeid != 0:
20 p1, p2 = self._repo.dirstate.parents()
21 self._rev = self._repo.changelog.rev(p1)
22 self._node = p1
23 return
24
25 self._node = self._repo.lookup(changeid)
20 self._node = self._repo.lookup(changeid)
26 self._rev = self._repo.changelog.rev(self._node)
21 self._rev = self._repo.changelog.rev(self._node)
27
22
28 def __str__(self):
23 def __str__(self):
29 return short(self.node())
24 return short(self.node())
30
25
31 def __repr__(self):
26 def __repr__(self):
32 return "<changectx %s>" % str(self)
27 return "<changectx %s>" % str(self)
33
28
34 def __hash__(self):
29 def __hash__(self):
35 try:
30 try:
36 return hash(self._rev)
31 return hash(self._rev)
37 except AttributeError:
32 except AttributeError:
38 return id(self)
33 return id(self)
39
34
40 def __eq__(self, other):
35 def __eq__(self, other):
41 try:
36 try:
42 return self._rev == other._rev
37 return self._rev == other._rev
43 except AttributeError:
38 except AttributeError:
44 return False
39 return False
45
40
46 def __ne__(self, other):
41 def __ne__(self, other):
47 return not (self == other)
42 return not (self == other)
48
43
49 def __nonzero__(self):
44 def __nonzero__(self):
50 return self._rev != nullrev
45 return self._rev != nullrev
51
46
52 def __getattr__(self, name):
47 def __getattr__(self, name):
53 if name == '_changeset':
48 if name == '_changeset':
54 self._changeset = self._repo.changelog.read(self.node())
49 self._changeset = self._repo.changelog.read(self.node())
55 return self._changeset
50 return self._changeset
56 elif name == '_manifest':
51 elif name == '_manifest':
57 self._manifest = self._repo.manifest.read(self._changeset[0])
52 self._manifest = self._repo.manifest.read(self._changeset[0])
58 return self._manifest
53 return self._manifest
59 elif name == '_manifestdelta':
54 elif name == '_manifestdelta':
60 md = self._repo.manifest.readdelta(self._changeset[0])
55 md = self._repo.manifest.readdelta(self._changeset[0])
61 self._manifestdelta = md
56 self._manifestdelta = md
62 return self._manifestdelta
57 return self._manifestdelta
63 else:
58 else:
64 raise AttributeError, name
59 raise AttributeError, name
65
60
66 def __contains__(self, key):
61 def __contains__(self, key):
67 return key in self._manifest
62 return key in self._manifest
68
63
69 def __getitem__(self, key):
64 def __getitem__(self, key):
70 return self.filectx(key)
65 return self.filectx(key)
71
66
72 def __iter__(self):
67 def __iter__(self):
73 a = self._manifest.keys()
68 a = self._manifest.keys()
74 a.sort()
69 a.sort()
75 for f in a:
70 for f in a:
76 yield f
71 yield f
77
72
78 def changeset(self): return self._changeset
73 def changeset(self): return self._changeset
79 def manifest(self): return self._manifest
74 def manifest(self): return self._manifest
80
75
81 def rev(self): return self._rev
76 def rev(self): return self._rev
82 def node(self): return self._node
77 def node(self): return self._node
83 def user(self): return self._changeset[1]
78 def user(self): return self._changeset[1]
84 def date(self): return self._changeset[2]
79 def date(self): return self._changeset[2]
85 def files(self): return self._changeset[3]
80 def files(self): return self._changeset[3]
86 def description(self): return self._changeset[4]
81 def description(self): return self._changeset[4]
87 def branch(self): return self._changeset[5].get("branch")
82 def branch(self): return self._changeset[5].get("branch")
88 def extra(self): return self._changeset[5]
83 def extra(self): return self._changeset[5]
89 def tags(self): return self._repo.nodetags(self._node)
84 def tags(self): return self._repo.nodetags(self._node)
90
85
91 def parents(self):
86 def parents(self):
92 """return contexts for each parent changeset"""
87 """return contexts for each parent changeset"""
93 p = self._repo.changelog.parents(self._node)
88 p = self._repo.changelog.parents(self._node)
94 return [changectx(self._repo, x) for x in p]
89 return [changectx(self._repo, x) for x in p]
95
90
96 def children(self):
91 def children(self):
97 """return contexts for each child changeset"""
92 """return contexts for each child changeset"""
98 c = self._repo.changelog.children(self._node)
93 c = self._repo.changelog.children(self._node)
99 return [changectx(self._repo, x) for x in c]
94 return [changectx(self._repo, x) for x in c]
100
95
101 def _fileinfo(self, path):
96 def _fileinfo(self, path):
102 if '_manifest' in self.__dict__:
97 if '_manifest' in self.__dict__:
103 try:
98 try:
104 return self._manifest[path], self._manifest.flags(path)
99 return self._manifest[path], self._manifest.flags(path)
105 except KeyError:
100 except KeyError:
106 raise revlog.LookupError(self._node, path,
101 raise revlog.LookupError(self._node, path,
107 _('not found in manifest'))
102 _('not found in manifest'))
108 if '_manifestdelta' in self.__dict__ or path in self.files():
103 if '_manifestdelta' in self.__dict__ or path in self.files():
109 if path in self._manifestdelta:
104 if path in self._manifestdelta:
110 return self._manifestdelta[path], self._manifestdelta.flags(path)
105 return self._manifestdelta[path], self._manifestdelta.flags(path)
111 node, flag = self._repo.manifest.find(self._changeset[0], path)
106 node, flag = self._repo.manifest.find(self._changeset[0], path)
112 if not node:
107 if not node:
113 raise revlog.LookupError(self._node, path,
108 raise revlog.LookupError(self._node, path,
114 _('not found in manifest'))
109 _('not found in manifest'))
115
110
116 return node, flag
111 return node, flag
117
112
118 def filenode(self, path):
113 def filenode(self, path):
119 return self._fileinfo(path)[0]
114 return self._fileinfo(path)[0]
120
115
121 def fileflags(self, path):
116 def fileflags(self, path):
122 try:
117 try:
123 return self._fileinfo(path)[1]
118 return self._fileinfo(path)[1]
124 except revlog.LookupError:
119 except revlog.LookupError:
125 return ''
120 return ''
126
121
127 def filectx(self, path, fileid=None, filelog=None):
122 def filectx(self, path, fileid=None, filelog=None):
128 """get a file context from this changeset"""
123 """get a file context from this changeset"""
129 if fileid is None:
124 if fileid is None:
130 fileid = self.filenode(path)
125 fileid = self.filenode(path)
131 return filectx(self._repo, path, fileid=fileid,
126 return filectx(self._repo, path, fileid=fileid,
132 changectx=self, filelog=filelog)
127 changectx=self, filelog=filelog)
133
128
134 def filectxs(self):
129 def filectxs(self):
135 """generate a file context for each file in this changeset's
130 """generate a file context for each file in this changeset's
136 manifest"""
131 manifest"""
137 mf = self.manifest()
132 mf = self.manifest()
138 m = mf.keys()
133 m = mf.keys()
139 m.sort()
134 m.sort()
140 for f in m:
135 for f in m:
141 yield self.filectx(f, fileid=mf[f])
136 yield self.filectx(f, fileid=mf[f])
142
137
143 def ancestor(self, c2):
138 def ancestor(self, c2):
144 """
139 """
145 return the ancestor context of self and c2
140 return the ancestor context of self and c2
146 """
141 """
147 n = self._repo.changelog.ancestor(self._node, c2._node)
142 n = self._repo.changelog.ancestor(self._node, c2._node)
148 return changectx(self._repo, n)
143 return changectx(self._repo, n)
149
144
150 class filectx(object):
145 class filectx(object):
151 """A filecontext object makes access to data related to a particular
146 """A filecontext object makes access to data related to a particular
152 filerevision convenient."""
147 filerevision convenient."""
153 def __init__(self, repo, path, changeid=None, fileid=None,
148 def __init__(self, repo, path, changeid=None, fileid=None,
154 filelog=None, changectx=None):
149 filelog=None, changectx=None):
155 """changeid can be a changeset revision, node, or tag.
150 """changeid can be a changeset revision, node, or tag.
156 fileid can be a file revision or node."""
151 fileid can be a file revision or node."""
157 self._repo = repo
152 self._repo = repo
158 self._path = path
153 self._path = path
159
154
160 assert (changeid is not None
155 assert (changeid is not None
161 or fileid is not None
156 or fileid is not None
162 or changectx is not None)
157 or changectx is not None)
163
158
164 if filelog:
159 if filelog:
165 self._filelog = filelog
160 self._filelog = filelog
166
161
167 if changeid is not None:
162 if changeid is not None:
168 self._changeid = changeid
163 self._changeid = changeid
169 if changectx is not None:
164 if changectx is not None:
170 self._changectx = changectx
165 self._changectx = changectx
171 if fileid is not None:
166 if fileid is not None:
172 self._fileid = fileid
167 self._fileid = fileid
173
168
174 def __getattr__(self, name):
169 def __getattr__(self, name):
175 if name == '_changectx':
170 if name == '_changectx':
176 self._changectx = changectx(self._repo, self._changeid)
171 self._changectx = changectx(self._repo, self._changeid)
177 return self._changectx
172 return self._changectx
178 elif name == '_filelog':
173 elif name == '_filelog':
179 self._filelog = self._repo.file(self._path)
174 self._filelog = self._repo.file(self._path)
180 return self._filelog
175 return self._filelog
181 elif name == '_changeid':
176 elif name == '_changeid':
182 if '_changectx' in self.__dict__:
177 if '_changectx' in self.__dict__:
183 self._changeid = self._changectx.rev()
178 self._changeid = self._changectx.rev()
184 else:
179 else:
185 self._changeid = self._filelog.linkrev(self._filenode)
180 self._changeid = self._filelog.linkrev(self._filenode)
186 return self._changeid
181 return self._changeid
187 elif name == '_filenode':
182 elif name == '_filenode':
188 if '_fileid' in self.__dict__:
183 if '_fileid' in self.__dict__:
189 self._filenode = self._filelog.lookup(self._fileid)
184 self._filenode = self._filelog.lookup(self._fileid)
190 else:
185 else:
191 self._filenode = self._changectx.filenode(self._path)
186 self._filenode = self._changectx.filenode(self._path)
192 return self._filenode
187 return self._filenode
193 elif name == '_filerev':
188 elif name == '_filerev':
194 self._filerev = self._filelog.rev(self._filenode)
189 self._filerev = self._filelog.rev(self._filenode)
195 return self._filerev
190 return self._filerev
196 elif name == '_repopath':
191 elif name == '_repopath':
197 self._repopath = self._path
192 self._repopath = self._path
198 return self._repopath
193 return self._repopath
199 else:
194 else:
200 raise AttributeError, name
195 raise AttributeError, name
201
196
202 def __nonzero__(self):
197 def __nonzero__(self):
203 try:
198 try:
204 n = self._filenode
199 n = self._filenode
205 return True
200 return True
206 except revlog.LookupError:
201 except revlog.LookupError:
207 # file is missing
202 # file is missing
208 return False
203 return False
209
204
210 def __str__(self):
205 def __str__(self):
211 return "%s@%s" % (self.path(), short(self.node()))
206 return "%s@%s" % (self.path(), short(self.node()))
212
207
213 def __repr__(self):
208 def __repr__(self):
214 return "<filectx %s>" % str(self)
209 return "<filectx %s>" % str(self)
215
210
216 def __hash__(self):
211 def __hash__(self):
217 try:
212 try:
218 return hash((self._path, self._fileid))
213 return hash((self._path, self._fileid))
219 except AttributeError:
214 except AttributeError:
220 return id(self)
215 return id(self)
221
216
222 def __eq__(self, other):
217 def __eq__(self, other):
223 try:
218 try:
224 return (self._path == other._path
219 return (self._path == other._path
225 and self._fileid == other._fileid)
220 and self._fileid == other._fileid)
226 except AttributeError:
221 except AttributeError:
227 return False
222 return False
228
223
229 def __ne__(self, other):
224 def __ne__(self, other):
230 return not (self == other)
225 return not (self == other)
231
226
232 def filectx(self, fileid):
227 def filectx(self, fileid):
233 '''opens an arbitrary revision of the file without
228 '''opens an arbitrary revision of the file without
234 opening a new filelog'''
229 opening a new filelog'''
235 return filectx(self._repo, self._path, fileid=fileid,
230 return filectx(self._repo, self._path, fileid=fileid,
236 filelog=self._filelog)
231 filelog=self._filelog)
237
232
238 def filerev(self): return self._filerev
233 def filerev(self): return self._filerev
239 def filenode(self): return self._filenode
234 def filenode(self): return self._filenode
240 def fileflags(self): return self._changectx.fileflags(self._path)
235 def fileflags(self): return self._changectx.fileflags(self._path)
241 def isexec(self): return 'x' in self.fileflags()
236 def isexec(self): return 'x' in self.fileflags()
242 def islink(self): return 'l' in self.fileflags()
237 def islink(self): return 'l' in self.fileflags()
243 def filelog(self): return self._filelog
238 def filelog(self): return self._filelog
244
239
245 def rev(self):
240 def rev(self):
246 if '_changectx' in self.__dict__:
241 if '_changectx' in self.__dict__:
247 return self._changectx.rev()
242 return self._changectx.rev()
248 if '_changeid' in self.__dict__:
243 if '_changeid' in self.__dict__:
249 return self._changectx.rev()
244 return self._changectx.rev()
250 return self._filelog.linkrev(self._filenode)
245 return self._filelog.linkrev(self._filenode)
251
246
252 def linkrev(self): return self._filelog.linkrev(self._filenode)
247 def linkrev(self): return self._filelog.linkrev(self._filenode)
253 def node(self): return self._changectx.node()
248 def node(self): return self._changectx.node()
254 def user(self): return self._changectx.user()
249 def user(self): return self._changectx.user()
255 def date(self): return self._changectx.date()
250 def date(self): return self._changectx.date()
256 def files(self): return self._changectx.files()
251 def files(self): return self._changectx.files()
257 def description(self): return self._changectx.description()
252 def description(self): return self._changectx.description()
258 def branch(self): return self._changectx.branch()
253 def branch(self): return self._changectx.branch()
259 def manifest(self): return self._changectx.manifest()
254 def manifest(self): return self._changectx.manifest()
260 def changectx(self): return self._changectx
255 def changectx(self): return self._changectx
261
256
262 def data(self): return self._filelog.read(self._filenode)
257 def data(self): return self._filelog.read(self._filenode)
263 def path(self): return self._path
258 def path(self): return self._path
264 def size(self): return self._filelog.size(self._filerev)
259 def size(self): return self._filelog.size(self._filerev)
265
260
266 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
261 def cmp(self, text): return self._filelog.cmp(self._filenode, text)
267
262
268 def renamed(self):
263 def renamed(self):
269 """check if file was actually renamed in this changeset revision
264 """check if file was actually renamed in this changeset revision
270
265
271 If rename logged in file revision, we report copy for changeset only
266 If rename logged in file revision, we report copy for changeset only
272 if file revisions linkrev points back to the changeset in question
267 if file revisions linkrev points back to the changeset in question
273 or both changeset parents contain different file revisions.
268 or both changeset parents contain different file revisions.
274 """
269 """
275
270
276 renamed = self._filelog.renamed(self._filenode)
271 renamed = self._filelog.renamed(self._filenode)
277 if not renamed:
272 if not renamed:
278 return renamed
273 return renamed
279
274
280 if self.rev() == self.linkrev():
275 if self.rev() == self.linkrev():
281 return renamed
276 return renamed
282
277
283 name = self.path()
278 name = self.path()
284 fnode = self._filenode
279 fnode = self._filenode
285 for p in self._changectx.parents():
280 for p in self._changectx.parents():
286 try:
281 try:
287 if fnode == p.filenode(name):
282 if fnode == p.filenode(name):
288 return None
283 return None
289 except revlog.LookupError:
284 except revlog.LookupError:
290 pass
285 pass
291 return renamed
286 return renamed
292
287
293 def parents(self):
288 def parents(self):
294 p = self._path
289 p = self._path
295 fl = self._filelog
290 fl = self._filelog
296 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
291 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
297
292
298 r = self._filelog.renamed(self._filenode)
293 r = self._filelog.renamed(self._filenode)
299 if r:
294 if r:
300 pl[0] = (r[0], r[1], None)
295 pl[0] = (r[0], r[1], None)
301
296
302 return [filectx(self._repo, p, fileid=n, filelog=l)
297 return [filectx(self._repo, p, fileid=n, filelog=l)
303 for p,n,l in pl if n != nullid]
298 for p,n,l in pl if n != nullid]
304
299
305 def children(self):
300 def children(self):
306 # hard for renames
301 # hard for renames
307 c = self._filelog.children(self._filenode)
302 c = self._filelog.children(self._filenode)
308 return [filectx(self._repo, self._path, fileid=x,
303 return [filectx(self._repo, self._path, fileid=x,
309 filelog=self._filelog) for x in c]
304 filelog=self._filelog) for x in c]
310
305
311 def annotate(self, follow=False, linenumber=None):
306 def annotate(self, follow=False, linenumber=None):
312 '''returns a list of tuples of (ctx, line) for each line
307 '''returns a list of tuples of (ctx, line) for each line
313 in the file, where ctx is the filectx of the node where
308 in the file, where ctx is the filectx of the node where
314 that line was last changed.
309 that line was last changed.
315 This returns tuples of ((ctx, linenumber), line) for each line,
310 This returns tuples of ((ctx, linenumber), line) for each line,
316 if "linenumber" parameter is NOT "None".
311 if "linenumber" parameter is NOT "None".
317 In such tuples, linenumber means one at the first appearance
312 In such tuples, linenumber means one at the first appearance
318 in the managed file.
313 in the managed file.
319 To reduce annotation cost,
314 To reduce annotation cost,
320 this returns fixed value(False is used) as linenumber,
315 this returns fixed value(False is used) as linenumber,
321 if "linenumber" parameter is "False".'''
316 if "linenumber" parameter is "False".'''
322
317
323 def decorate_compat(text, rev):
318 def decorate_compat(text, rev):
324 return ([rev] * len(text.splitlines()), text)
319 return ([rev] * len(text.splitlines()), text)
325
320
326 def without_linenumber(text, rev):
321 def without_linenumber(text, rev):
327 return ([(rev, False)] * len(text.splitlines()), text)
322 return ([(rev, False)] * len(text.splitlines()), text)
328
323
329 def with_linenumber(text, rev):
324 def with_linenumber(text, rev):
330 size = len(text.splitlines())
325 size = len(text.splitlines())
331 return ([(rev, i) for i in xrange(1, size + 1)], text)
326 return ([(rev, i) for i in xrange(1, size + 1)], text)
332
327
333 decorate = (((linenumber is None) and decorate_compat) or
328 decorate = (((linenumber is None) and decorate_compat) or
334 (linenumber and with_linenumber) or
329 (linenumber and with_linenumber) or
335 without_linenumber)
330 without_linenumber)
336
331
337 def pair(parent, child):
332 def pair(parent, child):
338 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
333 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
339 child[0][b1:b2] = parent[0][a1:a2]
334 child[0][b1:b2] = parent[0][a1:a2]
340 return child
335 return child
341
336
342 getlog = util.cachefunc(lambda x: self._repo.file(x))
337 getlog = util.cachefunc(lambda x: self._repo.file(x))
343 def getctx(path, fileid):
338 def getctx(path, fileid):
344 log = path == self._path and self._filelog or getlog(path)
339 log = path == self._path and self._filelog or getlog(path)
345 return filectx(self._repo, path, fileid=fileid, filelog=log)
340 return filectx(self._repo, path, fileid=fileid, filelog=log)
346 getctx = util.cachefunc(getctx)
341 getctx = util.cachefunc(getctx)
347
342
348 def parents(f):
343 def parents(f):
349 # we want to reuse filectx objects as much as possible
344 # we want to reuse filectx objects as much as possible
350 p = f._path
345 p = f._path
351 if f._filerev is None: # working dir
346 if f._filerev is None: # working dir
352 pl = [(n.path(), n.filerev()) for n in f.parents()]
347 pl = [(n.path(), n.filerev()) for n in f.parents()]
353 else:
348 else:
354 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
349 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
355
350
356 if follow:
351 if follow:
357 r = f.renamed()
352 r = f.renamed()
358 if r:
353 if r:
359 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
354 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
360
355
361 return [getctx(p, n) for p, n in pl if n != nullrev]
356 return [getctx(p, n) for p, n in pl if n != nullrev]
362
357
363 # use linkrev to find the first changeset where self appeared
358 # use linkrev to find the first changeset where self appeared
364 if self.rev() != self.linkrev():
359 if self.rev() != self.linkrev():
365 base = self.filectx(self.filerev())
360 base = self.filectx(self.filerev())
366 else:
361 else:
367 base = self
362 base = self
368
363
369 # find all ancestors
364 # find all ancestors
370 needed = {base: 1}
365 needed = {base: 1}
371 visit = [base]
366 visit = [base]
372 files = [base._path]
367 files = [base._path]
373 while visit:
368 while visit:
374 f = visit.pop(0)
369 f = visit.pop(0)
375 for p in parents(f):
370 for p in parents(f):
376 if p not in needed:
371 if p not in needed:
377 needed[p] = 1
372 needed[p] = 1
378 visit.append(p)
373 visit.append(p)
379 if p._path not in files:
374 if p._path not in files:
380 files.append(p._path)
375 files.append(p._path)
381 else:
376 else:
382 # count how many times we'll use this
377 # count how many times we'll use this
383 needed[p] += 1
378 needed[p] += 1
384
379
385 # sort by revision (per file) which is a topological order
380 # sort by revision (per file) which is a topological order
386 visit = []
381 visit = []
387 for f in files:
382 for f in files:
388 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
383 fn = [(n.rev(), n) for n in needed.keys() if n._path == f]
389 visit.extend(fn)
384 visit.extend(fn)
390 visit.sort()
385 visit.sort()
391 hist = {}
386 hist = {}
392
387
393 for r, f in visit:
388 for r, f in visit:
394 curr = decorate(f.data(), f)
389 curr = decorate(f.data(), f)
395 for p in parents(f):
390 for p in parents(f):
396 if p != nullid:
391 if p != nullid:
397 curr = pair(hist[p], curr)
392 curr = pair(hist[p], curr)
398 # trim the history of unneeded revs
393 # trim the history of unneeded revs
399 needed[p] -= 1
394 needed[p] -= 1
400 if not needed[p]:
395 if not needed[p]:
401 del hist[p]
396 del hist[p]
402 hist[f] = curr
397 hist[f] = curr
403
398
404 return zip(hist[f][0], hist[f][1].splitlines(1))
399 return zip(hist[f][0], hist[f][1].splitlines(1))
405
400
406 def ancestor(self, fc2):
401 def ancestor(self, fc2):
407 """
402 """
408 find the common ancestor file context, if any, of self, and fc2
403 find the common ancestor file context, if any, of self, and fc2
409 """
404 """
410
405
411 acache = {}
406 acache = {}
412
407
413 # prime the ancestor cache for the working directory
408 # prime the ancestor cache for the working directory
414 for c in (self, fc2):
409 for c in (self, fc2):
415 if c._filerev == None:
410 if c._filerev == None:
416 pl = [(n.path(), n.filenode()) for n in c.parents()]
411 pl = [(n.path(), n.filenode()) for n in c.parents()]
417 acache[(c._path, None)] = pl
412 acache[(c._path, None)] = pl
418
413
419 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
414 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
420 def parents(vertex):
415 def parents(vertex):
421 if vertex in acache:
416 if vertex in acache:
422 return acache[vertex]
417 return acache[vertex]
423 f, n = vertex
418 f, n = vertex
424 if f not in flcache:
419 if f not in flcache:
425 flcache[f] = self._repo.file(f)
420 flcache[f] = self._repo.file(f)
426 fl = flcache[f]
421 fl = flcache[f]
427 pl = [(f, p) for p in fl.parents(n) if p != nullid]
422 pl = [(f, p) for p in fl.parents(n) if p != nullid]
428 re = fl.renamed(n)
423 re = fl.renamed(n)
429 if re:
424 if re:
430 pl.append(re)
425 pl.append(re)
431 acache[vertex] = pl
426 acache[vertex] = pl
432 return pl
427 return pl
433
428
434 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
429 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
435 v = ancestor.ancestor(a, b, parents)
430 v = ancestor.ancestor(a, b, parents)
436 if v:
431 if v:
437 f, n = v
432 f, n = v
438 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
433 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
439
434
440 return None
435 return None
441
436
442 class workingctx(changectx):
437 class workingctx(changectx):
443 """A workingctx object makes access to data related to
438 """A workingctx object makes access to data related to
444 the current working directory convenient.
439 the current working directory convenient.
445 parents - a pair of parent nodeids, or None to use the dirstate.
440 parents - a pair of parent nodeids, or None to use the dirstate.
446 date - any valid date string or (unixtime, offset), or None.
441 date - any valid date string or (unixtime, offset), or None.
447 user - username string, or None.
442 user - username string, or None.
448 extra - a dictionary of extra values, or None.
443 extra - a dictionary of extra values, or None.
449 changes - a list of file lists as returned by localrepo.status()
444 changes - a list of file lists as returned by localrepo.status()
450 or None to use the repository status.
445 or None to use the repository status.
451 """
446 """
452 def __init__(self, repo, parents=None, text="", user=None, date=None,
447 def __init__(self, repo, parents=None, text="", user=None, date=None,
453 extra=None, changes=None):
448 extra=None, changes=None):
454 self._repo = repo
449 self._repo = repo
455 self._rev = None
450 self._rev = None
456 self._node = None
451 self._node = None
457 self._text = text
452 self._text = text
458 if date:
453 if date:
459 self._date = util.parsedate(date)
454 self._date = util.parsedate(date)
460 else:
455 else:
461 self._date = util.makedate()
456 self._date = util.makedate()
462 if user:
457 if user:
463 self._user = user
458 self._user = user
464 else:
459 else:
465 self._user = self._repo.ui.username()
460 self._user = self._repo.ui.username()
466 if parents:
461 if parents:
467 p1, p2 = parents
462 p1, p2 = parents
468 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
463 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
469 if changes:
464 if changes:
470 self._status = list(changes)
465 self._status = list(changes)
471
466
472 self._extra = {}
467 self._extra = {}
473 if extra:
468 if extra:
474 self._extra = extra.copy()
469 self._extra = extra.copy()
475 if 'branch' not in self._extra:
470 if 'branch' not in self._extra:
476 branch = self._repo.dirstate.branch()
471 branch = self._repo.dirstate.branch()
477 try:
472 try:
478 branch = branch.decode('UTF-8').encode('UTF-8')
473 branch = branch.decode('UTF-8').encode('UTF-8')
479 except UnicodeDecodeError:
474 except UnicodeDecodeError:
480 raise util.Abort(_('branch name not in UTF-8!'))
475 raise util.Abort(_('branch name not in UTF-8!'))
481 self._extra['branch'] = branch
476 self._extra['branch'] = branch
482 if self._extra['branch'] == '':
477 if self._extra['branch'] == '':
483 self._extra['branch'] = 'default'
478 self._extra['branch'] = 'default'
484
479
485 def __str__(self):
480 def __str__(self):
486 return str(self._parents[0]) + "+"
481 return str(self._parents[0]) + "+"
487
482
488 def __nonzero__(self):
483 def __nonzero__(self):
489 return True
484 return True
490
485
491 def __getattr__(self, name):
486 def __getattr__(self, name):
492 if name == '_parents':
487 if name == '_parents':
493 self._parents = self._repo.parents()
488 self._parents = self._repo.parents()
494 return self._parents
489 return self._parents
495 if name == '_status':
490 if name == '_status':
496 self._status = self._repo.status()
491 self._status = self._repo.status()
497 return self._status
492 return self._status
498 if name == '_manifest':
493 if name == '_manifest':
499 self._buildmanifest()
494 self._buildmanifest()
500 return self._manifest
495 return self._manifest
501 else:
496 else:
502 raise AttributeError, name
497 raise AttributeError, name
503
498
504 def _buildmanifest(self):
499 def _buildmanifest(self):
505 """generate a manifest corresponding to the working directory"""
500 """generate a manifest corresponding to the working directory"""
506
501
507 man = self._parents[0].manifest().copy()
502 man = self._parents[0].manifest().copy()
508 copied = self._repo.dirstate.copies()
503 copied = self._repo.dirstate.copies()
509 is_exec = util.execfunc(self._repo.root,
504 is_exec = util.execfunc(self._repo.root,
510 lambda p: man.execf(copied.get(p,p)))
505 lambda p: man.execf(copied.get(p,p)))
511 is_link = util.linkfunc(self._repo.root,
506 is_link = util.linkfunc(self._repo.root,
512 lambda p: man.linkf(copied.get(p,p)))
507 lambda p: man.linkf(copied.get(p,p)))
513 modified, added, removed, deleted, unknown = self._status[:5]
508 modified, added, removed, deleted, unknown = self._status[:5]
514 for i, l in (("a", added), ("m", modified), ("u", unknown)):
509 for i, l in (("a", added), ("m", modified), ("u", unknown)):
515 for f in l:
510 for f in l:
516 man[f] = man.get(copied.get(f, f), nullid) + i
511 man[f] = man.get(copied.get(f, f), nullid) + i
517 try:
512 try:
518 man.set(f, is_exec(f), is_link(f))
513 man.set(f, is_exec(f), is_link(f))
519 except OSError:
514 except OSError:
520 pass
515 pass
521
516
522 for f in deleted + removed:
517 for f in deleted + removed:
523 if f in man:
518 if f in man:
524 del man[f]
519 del man[f]
525
520
526 self._manifest = man
521 self._manifest = man
527
522
528 def manifest(self): return self._manifest
523 def manifest(self): return self._manifest
529
524
530 def user(self): return self._user
525 def user(self): return self._user
531 def date(self): return self._date
526 def date(self): return self._date
532 def description(self): return self._text
527 def description(self): return self._text
533 def files(self):
528 def files(self):
534 f = self.modified() + self.added() + self.removed()
529 f = self.modified() + self.added() + self.removed()
535 f.sort()
530 f.sort()
536 return f
531 return f
537
532
538 def modified(self): return self._status[0]
533 def modified(self): return self._status[0]
539 def added(self): return self._status[1]
534 def added(self): return self._status[1]
540 def removed(self): return self._status[2]
535 def removed(self): return self._status[2]
541 def deleted(self): return self._status[3]
536 def deleted(self): return self._status[3]
542 def unknown(self): return self._status[4]
537 def unknown(self): return self._status[4]
543 def clean(self): return self._status[5]
538 def clean(self): return self._status[5]
544 def branch(self): return self._extra['branch']
539 def branch(self): return self._extra['branch']
545 def extra(self): return self._extra
540 def extra(self): return self._extra
546
541
547 def tags(self):
542 def tags(self):
548 t = []
543 t = []
549 [t.extend(p.tags()) for p in self.parents()]
544 [t.extend(p.tags()) for p in self.parents()]
550 return t
545 return t
551
546
552 def parents(self):
547 def parents(self):
553 """return contexts for each parent changeset"""
548 """return contexts for each parent changeset"""
554 return self._parents
549 return self._parents
555
550
556 def children(self):
551 def children(self):
557 return []
552 return []
558
553
559 def fileflags(self, path):
554 def fileflags(self, path):
560 if '_manifest' in self.__dict__:
555 if '_manifest' in self.__dict__:
561 try:
556 try:
562 return self._manifest.flags(path)
557 return self._manifest.flags(path)
563 except KeyError:
558 except KeyError:
564 return ''
559 return ''
565
560
566 pnode = self._parents[0].changeset()[0]
561 pnode = self._parents[0].changeset()[0]
567 orig = self._repo.dirstate.copies().get(path, path)
562 orig = self._repo.dirstate.copies().get(path, path)
568 node, flag = self._repo.manifest.find(pnode, orig)
563 node, flag = self._repo.manifest.find(pnode, orig)
569 is_link = util.linkfunc(self._repo.root,
564 is_link = util.linkfunc(self._repo.root,
570 lambda p: flag and 'l' in flag)
565 lambda p: flag and 'l' in flag)
571 is_exec = util.execfunc(self._repo.root,
566 is_exec = util.execfunc(self._repo.root,
572 lambda p: flag and 'x' in flag)
567 lambda p: flag and 'x' in flag)
573 try:
568 try:
574 return (is_link(path) and 'l' or '') + (is_exec(path) and 'x' or '')
569 return (is_link(path) and 'l' or '') + (is_exec(path) and 'x' or '')
575 except OSError:
570 except OSError:
576 pass
571 pass
577
572
578 if not node or path in self.deleted() or path in self.removed():
573 if not node or path in self.deleted() or path in self.removed():
579 return ''
574 return ''
580 return flag
575 return flag
581
576
582 def filectx(self, path, filelog=None):
577 def filectx(self, path, filelog=None):
583 """get a file context from the working directory"""
578 """get a file context from the working directory"""
584 return workingfilectx(self._repo, path, workingctx=self,
579 return workingfilectx(self._repo, path, workingctx=self,
585 filelog=filelog)
580 filelog=filelog)
586
581
587 def ancestor(self, c2):
582 def ancestor(self, c2):
588 """return the ancestor context of self and c2"""
583 """return the ancestor context of self and c2"""
589 return self._parents[0].ancestor(c2) # punt on two parents for now
584 return self._parents[0].ancestor(c2) # punt on two parents for now
590
585
591 class workingfilectx(filectx):
586 class workingfilectx(filectx):
592 """A workingfilectx object makes access to data related to a particular
587 """A workingfilectx object makes access to data related to a particular
593 file in the working directory convenient."""
588 file in the working directory convenient."""
594 def __init__(self, repo, path, filelog=None, workingctx=None):
589 def __init__(self, repo, path, filelog=None, workingctx=None):
595 """changeid can be a changeset revision, node, or tag.
590 """changeid can be a changeset revision, node, or tag.
596 fileid can be a file revision or node."""
591 fileid can be a file revision or node."""
597 self._repo = repo
592 self._repo = repo
598 self._path = path
593 self._path = path
599 self._changeid = None
594 self._changeid = None
600 self._filerev = self._filenode = None
595 self._filerev = self._filenode = None
601
596
602 if filelog:
597 if filelog:
603 self._filelog = filelog
598 self._filelog = filelog
604 if workingctx:
599 if workingctx:
605 self._changectx = workingctx
600 self._changectx = workingctx
606
601
607 def __getattr__(self, name):
602 def __getattr__(self, name):
608 if name == '_changectx':
603 if name == '_changectx':
609 self._changectx = workingctx(self._repo)
604 self._changectx = workingctx(self._repo)
610 return self._changectx
605 return self._changectx
611 elif name == '_repopath':
606 elif name == '_repopath':
612 self._repopath = (self._repo.dirstate.copied(self._path)
607 self._repopath = (self._repo.dirstate.copied(self._path)
613 or self._path)
608 or self._path)
614 return self._repopath
609 return self._repopath
615 elif name == '_filelog':
610 elif name == '_filelog':
616 self._filelog = self._repo.file(self._repopath)
611 self._filelog = self._repo.file(self._repopath)
617 return self._filelog
612 return self._filelog
618 else:
613 else:
619 raise AttributeError, name
614 raise AttributeError, name
620
615
621 def __nonzero__(self):
616 def __nonzero__(self):
622 return True
617 return True
623
618
624 def __str__(self):
619 def __str__(self):
625 return "%s@%s" % (self.path(), self._changectx)
620 return "%s@%s" % (self.path(), self._changectx)
626
621
627 def filectx(self, fileid):
622 def filectx(self, fileid):
628 '''opens an arbitrary revision of the file without
623 '''opens an arbitrary revision of the file without
629 opening a new filelog'''
624 opening a new filelog'''
630 return filectx(self._repo, self._repopath, fileid=fileid,
625 return filectx(self._repo, self._repopath, fileid=fileid,
631 filelog=self._filelog)
626 filelog=self._filelog)
632
627
633 def rev(self):
628 def rev(self):
634 if '_changectx' in self.__dict__:
629 if '_changectx' in self.__dict__:
635 return self._changectx.rev()
630 return self._changectx.rev()
636 return self._filelog.linkrev(self._filenode)
631 return self._filelog.linkrev(self._filenode)
637
632
638 def data(self): return self._repo.wread(self._path)
633 def data(self): return self._repo.wread(self._path)
639 def renamed(self):
634 def renamed(self):
640 rp = self._repopath
635 rp = self._repopath
641 if rp == self._path:
636 if rp == self._path:
642 return None
637 return None
643 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
638 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
644
639
645 def parents(self):
640 def parents(self):
646 '''return parent filectxs, following copies if necessary'''
641 '''return parent filectxs, following copies if necessary'''
647 p = self._path
642 p = self._path
648 rp = self._repopath
643 rp = self._repopath
649 pcl = self._changectx._parents
644 pcl = self._changectx._parents
650 fl = self._filelog
645 fl = self._filelog
651 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
646 pl = [(rp, pcl[0]._manifest.get(rp, nullid), fl)]
652 if len(pcl) > 1:
647 if len(pcl) > 1:
653 if rp != p:
648 if rp != p:
654 fl = None
649 fl = None
655 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
650 pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
656
651
657 return [filectx(self._repo, p, fileid=n, filelog=l)
652 return [filectx(self._repo, p, fileid=n, filelog=l)
658 for p,n,l in pl if n != nullid]
653 for p,n,l in pl if n != nullid]
659
654
660 def children(self):
655 def children(self):
661 return []
656 return []
662
657
663 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
658 def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
664 def date(self):
659 def date(self):
665 t, tz = self._changectx.date()
660 t, tz = self._changectx.date()
666 try:
661 try:
667 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
662 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
668 except OSError, err:
663 except OSError, err:
669 if err.errno != errno.ENOENT: raise
664 if err.errno != errno.ENOENT: raise
670 return (t, tz)
665 return (t, tz)
671
666
672 def cmp(self, text): return self._repo.wread(self._path) == text
667 def cmp(self, text): return self._repo.wread(self._path) == text
673
668
674 class memctx(object):
669 class memctx(object):
675 """A memctx is a subset of changectx supposed to be built on memory
670 """A memctx is a subset of changectx supposed to be built on memory
676 and passed to commit functions.
671 and passed to commit functions.
677
672
678 NOTE: this interface and the related memfilectx are experimental and
673 NOTE: this interface and the related memfilectx are experimental and
679 may change without notice.
674 may change without notice.
680
675
681 parents - a pair of parent nodeids.
676 parents - a pair of parent nodeids.
682 filectxfn - a callable taking (repo, memctx, path) arguments and
677 filectxfn - a callable taking (repo, memctx, path) arguments and
683 returning a memctx object.
678 returning a memctx object.
684 date - any valid date string or (unixtime, offset), or None.
679 date - any valid date string or (unixtime, offset), or None.
685 user - username string, or None.
680 user - username string, or None.
686 extra - a dictionary of extra values, or None.
681 extra - a dictionary of extra values, or None.
687 """
682 """
688 def __init__(self, repo, parents, text, files, filectxfn, user=None,
683 def __init__(self, repo, parents, text, files, filectxfn, user=None,
689 date=None, extra=None):
684 date=None, extra=None):
690 self._repo = repo
685 self._repo = repo
691 self._rev = None
686 self._rev = None
692 self._node = None
687 self._node = None
693 self._text = text
688 self._text = text
694 self._date = date and util.parsedate(date) or util.makedate()
689 self._date = date and util.parsedate(date) or util.makedate()
695 self._user = user or self._repo.ui.username()
690 self._user = user or self._repo.ui.username()
696 parents = [(p or nullid) for p in parents]
691 parents = [(p or nullid) for p in parents]
697 p1, p2 = parents
692 p1, p2 = parents
698 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
693 self._parents = [self._repo.changectx(p) for p in (p1, p2)]
699 files = list(files)
694 files = list(files)
700 files.sort()
695 files.sort()
701 self._status = [files, [], [], [], []]
696 self._status = [files, [], [], [], []]
702 self._filectxfn = filectxfn
697 self._filectxfn = filectxfn
703
698
704 self._extra = extra and extra.copy() or {}
699 self._extra = extra and extra.copy() or {}
705 if 'branch' not in self._extra:
700 if 'branch' not in self._extra:
706 self._extra['branch'] = 'default'
701 self._extra['branch'] = 'default'
707 elif self._extra.get('branch') == '':
702 elif self._extra.get('branch') == '':
708 self._extra['branch'] = 'default'
703 self._extra['branch'] = 'default'
709
704
710 def __str__(self):
705 def __str__(self):
711 return str(self._parents[0]) + "+"
706 return str(self._parents[0]) + "+"
712
707
713 def __nonzero__(self):
708 def __nonzero__(self):
714 return True
709 return True
715
710
716 def user(self): return self._user
711 def user(self): return self._user
717 def date(self): return self._date
712 def date(self): return self._date
718 def description(self): return self._text
713 def description(self): return self._text
719 def files(self): return self.modified()
714 def files(self): return self.modified()
720 def modified(self): return self._status[0]
715 def modified(self): return self._status[0]
721 def added(self): return self._status[1]
716 def added(self): return self._status[1]
722 def removed(self): return self._status[2]
717 def removed(self): return self._status[2]
723 def deleted(self): return self._status[3]
718 def deleted(self): return self._status[3]
724 def unknown(self): return self._status[4]
719 def unknown(self): return self._status[4]
725 def clean(self): return self._status[5]
720 def clean(self): return self._status[5]
726 def branch(self): return self._extra['branch']
721 def branch(self): return self._extra['branch']
727 def extra(self): return self._extra
722 def extra(self): return self._extra
728
723
729 def parents(self):
724 def parents(self):
730 """return contexts for each parent changeset"""
725 """return contexts for each parent changeset"""
731 return self._parents
726 return self._parents
732
727
733 def filectx(self, path, filelog=None):
728 def filectx(self, path, filelog=None):
734 """get a file context from the working directory"""
729 """get a file context from the working directory"""
735 return self._filectxfn(self._repo, self, path)
730 return self._filectxfn(self._repo, self, path)
736
731
737 class memfilectx(object):
732 class memfilectx(object):
738 """A memfilectx is a subset of filectx supposed to be built by client
733 """A memfilectx is a subset of filectx supposed to be built by client
739 code and passed to commit functions.
734 code and passed to commit functions.
740 """
735 """
741 def __init__(self, path, data, islink, isexec, copied):
736 def __init__(self, path, data, islink, isexec, copied):
742 """copied is the source file path, or None."""
737 """copied is the source file path, or None."""
743 self._path = path
738 self._path = path
744 self._data = data
739 self._data = data
745 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
740 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
746 self._copied = None
741 self._copied = None
747 if copied:
742 if copied:
748 self._copied = (copied, nullid)
743 self._copied = (copied, nullid)
749
744
750 def __nonzero__(self): return True
745 def __nonzero__(self): return True
751 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
746 def __str__(self): return "%s@%s" % (self.path(), self._changectx)
752 def path(self): return self._path
747 def path(self): return self._path
753 def data(self): return self._data
748 def data(self): return self._data
754 def fileflags(self): return self._flags
749 def fileflags(self): return self._flags
755 def isexec(self): return 'x' in self._flags
750 def isexec(self): return 'x' in self._flags
756 def islink(self): return 'l' in self._flags
751 def islink(self): return 'l' in self._flags
757 def renamed(self): return self._copied
752 def renamed(self): return self._copied
758
753
General Comments 0
You need to be logged in to leave comments. Login now