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