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