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