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