##// END OF EJS Templates
context: use 'changectx.dirs()' in 'walk()' for directory patterns...
FUJIWARA Katsunori -
r16145:616c2e27 stable
parent child Browse files
Show More
@@ -1,1195 +1,1196 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 or any later version.
6 # GNU General Public License version 2 or any later version.
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, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import match as matchmod
11 import match as matchmod
12 import os, errno, stat
12 import os, errno, stat
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15
15
16 class changectx(object):
16 class changectx(object):
17 """A changecontext object makes access to data related to a particular
17 """A changecontext object makes access to data related to a particular
18 changeset convenient."""
18 changeset convenient."""
19 def __init__(self, repo, changeid=''):
19 def __init__(self, repo, changeid=''):
20 """changeid is a revision number, node, or tag"""
20 """changeid is a revision number, node, or tag"""
21 if changeid == '':
21 if changeid == '':
22 changeid = '.'
22 changeid = '.'
23 self._repo = repo
23 self._repo = repo
24 if isinstance(changeid, (long, int)):
24 if isinstance(changeid, (long, int)):
25 self._rev = changeid
25 self._rev = changeid
26 self._node = self._repo.changelog.node(changeid)
26 self._node = self._repo.changelog.node(changeid)
27 else:
27 else:
28 self._node = self._repo.lookup(changeid)
28 self._node = self._repo.lookup(changeid)
29 self._rev = self._repo.changelog.rev(self._node)
29 self._rev = self._repo.changelog.rev(self._node)
30
30
31 def __str__(self):
31 def __str__(self):
32 return short(self.node())
32 return short(self.node())
33
33
34 def __int__(self):
34 def __int__(self):
35 return self.rev()
35 return self.rev()
36
36
37 def __repr__(self):
37 def __repr__(self):
38 return "<changectx %s>" % str(self)
38 return "<changectx %s>" % str(self)
39
39
40 def __hash__(self):
40 def __hash__(self):
41 try:
41 try:
42 return hash(self._rev)
42 return hash(self._rev)
43 except AttributeError:
43 except AttributeError:
44 return id(self)
44 return id(self)
45
45
46 def __eq__(self, other):
46 def __eq__(self, other):
47 try:
47 try:
48 return self._rev == other._rev
48 return self._rev == other._rev
49 except AttributeError:
49 except AttributeError:
50 return False
50 return False
51
51
52 def __ne__(self, other):
52 def __ne__(self, other):
53 return not (self == other)
53 return not (self == other)
54
54
55 def __nonzero__(self):
55 def __nonzero__(self):
56 return self._rev != nullrev
56 return self._rev != nullrev
57
57
58 @propertycache
58 @propertycache
59 def _changeset(self):
59 def _changeset(self):
60 return self._repo.changelog.read(self.node())
60 return self._repo.changelog.read(self.node())
61
61
62 @propertycache
62 @propertycache
63 def _manifest(self):
63 def _manifest(self):
64 return self._repo.manifest.read(self._changeset[0])
64 return self._repo.manifest.read(self._changeset[0])
65
65
66 @propertycache
66 @propertycache
67 def _manifestdelta(self):
67 def _manifestdelta(self):
68 return self._repo.manifest.readdelta(self._changeset[0])
68 return self._repo.manifest.readdelta(self._changeset[0])
69
69
70 @propertycache
70 @propertycache
71 def _parents(self):
71 def _parents(self):
72 p = self._repo.changelog.parentrevs(self._rev)
72 p = self._repo.changelog.parentrevs(self._rev)
73 if p[1] == nullrev:
73 if p[1] == nullrev:
74 p = p[:-1]
74 p = p[:-1]
75 return [changectx(self._repo, x) for x in p]
75 return [changectx(self._repo, x) for x in p]
76
76
77 @propertycache
77 @propertycache
78 def substate(self):
78 def substate(self):
79 return subrepo.state(self, self._repo.ui)
79 return subrepo.state(self, self._repo.ui)
80
80
81 def __contains__(self, key):
81 def __contains__(self, key):
82 return key in self._manifest
82 return key in self._manifest
83
83
84 def __getitem__(self, key):
84 def __getitem__(self, key):
85 return self.filectx(key)
85 return self.filectx(key)
86
86
87 def __iter__(self):
87 def __iter__(self):
88 for f in sorted(self._manifest):
88 for f in sorted(self._manifest):
89 yield f
89 yield f
90
90
91 def changeset(self):
91 def changeset(self):
92 return self._changeset
92 return self._changeset
93 def manifest(self):
93 def manifest(self):
94 return self._manifest
94 return self._manifest
95 def manifestnode(self):
95 def manifestnode(self):
96 return self._changeset[0]
96 return self._changeset[0]
97
97
98 def rev(self):
98 def rev(self):
99 return self._rev
99 return self._rev
100 def node(self):
100 def node(self):
101 return self._node
101 return self._node
102 def hex(self):
102 def hex(self):
103 return hex(self._node)
103 return hex(self._node)
104 def user(self):
104 def user(self):
105 return self._changeset[1]
105 return self._changeset[1]
106 def date(self):
106 def date(self):
107 return self._changeset[2]
107 return self._changeset[2]
108 def files(self):
108 def files(self):
109 return self._changeset[3]
109 return self._changeset[3]
110 def description(self):
110 def description(self):
111 return self._changeset[4]
111 return self._changeset[4]
112 def branch(self):
112 def branch(self):
113 return encoding.tolocal(self._changeset[5].get("branch"))
113 return encoding.tolocal(self._changeset[5].get("branch"))
114 def extra(self):
114 def extra(self):
115 return self._changeset[5]
115 return self._changeset[5]
116 def tags(self):
116 def tags(self):
117 return self._repo.nodetags(self._node)
117 return self._repo.nodetags(self._node)
118 def bookmarks(self):
118 def bookmarks(self):
119 return self._repo.nodebookmarks(self._node)
119 return self._repo.nodebookmarks(self._node)
120 def phase(self):
120 def phase(self):
121 if self._rev == -1:
121 if self._rev == -1:
122 return phases.public
122 return phases.public
123 if self._rev >= len(self._repo._phaserev):
123 if self._rev >= len(self._repo._phaserev):
124 # outdated cache
124 # outdated cache
125 del self._repo._phaserev
125 del self._repo._phaserev
126 return self._repo._phaserev[self._rev]
126 return self._repo._phaserev[self._rev]
127 def phasestr(self):
127 def phasestr(self):
128 return phases.phasenames[self.phase()]
128 return phases.phasenames[self.phase()]
129 def mutable(self):
129 def mutable(self):
130 return self._repo._phaserev[self._rev] > phases.public
130 return self._repo._phaserev[self._rev] > phases.public
131 def hidden(self):
131 def hidden(self):
132 return self._rev in self._repo.changelog.hiddenrevs
132 return self._rev in self._repo.changelog.hiddenrevs
133
133
134 def parents(self):
134 def parents(self):
135 """return contexts for each parent changeset"""
135 """return contexts for each parent changeset"""
136 return self._parents
136 return self._parents
137
137
138 def p1(self):
138 def p1(self):
139 return self._parents[0]
139 return self._parents[0]
140
140
141 def p2(self):
141 def p2(self):
142 if len(self._parents) == 2:
142 if len(self._parents) == 2:
143 return self._parents[1]
143 return self._parents[1]
144 return changectx(self._repo, -1)
144 return changectx(self._repo, -1)
145
145
146 def children(self):
146 def children(self):
147 """return contexts for each child changeset"""
147 """return contexts for each child changeset"""
148 c = self._repo.changelog.children(self._node)
148 c = self._repo.changelog.children(self._node)
149 return [changectx(self._repo, x) for x in c]
149 return [changectx(self._repo, x) for x in c]
150
150
151 def ancestors(self):
151 def ancestors(self):
152 for a in self._repo.changelog.ancestors(self._rev):
152 for a in self._repo.changelog.ancestors(self._rev):
153 yield changectx(self._repo, a)
153 yield changectx(self._repo, a)
154
154
155 def descendants(self):
155 def descendants(self):
156 for d in self._repo.changelog.descendants(self._rev):
156 for d in self._repo.changelog.descendants(self._rev):
157 yield changectx(self._repo, d)
157 yield changectx(self._repo, d)
158
158
159 def _fileinfo(self, path):
159 def _fileinfo(self, path):
160 if '_manifest' in self.__dict__:
160 if '_manifest' in self.__dict__:
161 try:
161 try:
162 return self._manifest[path], self._manifest.flags(path)
162 return self._manifest[path], self._manifest.flags(path)
163 except KeyError:
163 except KeyError:
164 raise error.LookupError(self._node, path,
164 raise error.LookupError(self._node, path,
165 _('not found in manifest'))
165 _('not found in manifest'))
166 if '_manifestdelta' in self.__dict__ or path in self.files():
166 if '_manifestdelta' in self.__dict__ or path in self.files():
167 if path in self._manifestdelta:
167 if path in self._manifestdelta:
168 return self._manifestdelta[path], self._manifestdelta.flags(path)
168 return self._manifestdelta[path], self._manifestdelta.flags(path)
169 node, flag = self._repo.manifest.find(self._changeset[0], path)
169 node, flag = self._repo.manifest.find(self._changeset[0], path)
170 if not node:
170 if not node:
171 raise error.LookupError(self._node, path,
171 raise error.LookupError(self._node, path,
172 _('not found in manifest'))
172 _('not found in manifest'))
173
173
174 return node, flag
174 return node, flag
175
175
176 def filenode(self, path):
176 def filenode(self, path):
177 return self._fileinfo(path)[0]
177 return self._fileinfo(path)[0]
178
178
179 def flags(self, path):
179 def flags(self, path):
180 try:
180 try:
181 return self._fileinfo(path)[1]
181 return self._fileinfo(path)[1]
182 except error.LookupError:
182 except error.LookupError:
183 return ''
183 return ''
184
184
185 def filectx(self, path, fileid=None, filelog=None):
185 def filectx(self, path, fileid=None, filelog=None):
186 """get a file context from this changeset"""
186 """get a file context from this changeset"""
187 if fileid is None:
187 if fileid is None:
188 fileid = self.filenode(path)
188 fileid = self.filenode(path)
189 return filectx(self._repo, path, fileid=fileid,
189 return filectx(self._repo, path, fileid=fileid,
190 changectx=self, filelog=filelog)
190 changectx=self, filelog=filelog)
191
191
192 def ancestor(self, c2):
192 def ancestor(self, c2):
193 """
193 """
194 return the ancestor context of self and c2
194 return the ancestor context of self and c2
195 """
195 """
196 # deal with workingctxs
196 # deal with workingctxs
197 n2 = c2._node
197 n2 = c2._node
198 if n2 is None:
198 if n2 is None:
199 n2 = c2._parents[0]._node
199 n2 = c2._parents[0]._node
200 n = self._repo.changelog.ancestor(self._node, n2)
200 n = self._repo.changelog.ancestor(self._node, n2)
201 return changectx(self._repo, n)
201 return changectx(self._repo, n)
202
202
203 def walk(self, match):
203 def walk(self, match):
204 fset = set(match.files())
204 fset = set(match.files())
205 # for dirstate.walk, files=['.'] means "walk the whole tree".
205 # for dirstate.walk, files=['.'] means "walk the whole tree".
206 # follow that here, too
206 # follow that here, too
207 fset.discard('.')
207 fset.discard('.')
208 for fn in self:
208 for fn in self:
209 for ffn in fset:
209 if fn in fset:
210 # match if the file is the exact name or a directory
210 # specified pattern is the exact name
211 if ffn == fn or fn.startswith("%s/" % ffn):
211 fset.remove(fn)
212 fset.remove(ffn)
213 break
214 if match(fn):
212 if match(fn):
215 yield fn
213 yield fn
216 for fn in sorted(fset):
214 for fn in sorted(fset):
215 if fn in self._dirs:
216 # specified pattern is a directory
217 continue
217 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
218 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
218 yield fn
219 yield fn
219
220
220 def sub(self, path):
221 def sub(self, path):
221 return subrepo.subrepo(self, path)
222 return subrepo.subrepo(self, path)
222
223
223 def match(self, pats=[], include=None, exclude=None, default='glob'):
224 def match(self, pats=[], include=None, exclude=None, default='glob'):
224 r = self._repo
225 r = self._repo
225 return matchmod.match(r.root, r.getcwd(), pats,
226 return matchmod.match(r.root, r.getcwd(), pats,
226 include, exclude, default,
227 include, exclude, default,
227 auditor=r.auditor, ctx=self)
228 auditor=r.auditor, ctx=self)
228
229
229 def diff(self, ctx2=None, match=None, **opts):
230 def diff(self, ctx2=None, match=None, **opts):
230 """Returns a diff generator for the given contexts and matcher"""
231 """Returns a diff generator for the given contexts and matcher"""
231 if ctx2 is None:
232 if ctx2 is None:
232 ctx2 = self.p1()
233 ctx2 = self.p1()
233 if ctx2 is not None and not isinstance(ctx2, changectx):
234 if ctx2 is not None and not isinstance(ctx2, changectx):
234 ctx2 = self._repo[ctx2]
235 ctx2 = self._repo[ctx2]
235 diffopts = patch.diffopts(self._repo.ui, opts)
236 diffopts = patch.diffopts(self._repo.ui, opts)
236 return patch.diff(self._repo, ctx2.node(), self.node(),
237 return patch.diff(self._repo, ctx2.node(), self.node(),
237 match=match, opts=diffopts)
238 match=match, opts=diffopts)
238
239
239 @propertycache
240 @propertycache
240 def _dirs(self):
241 def _dirs(self):
241 dirs = set()
242 dirs = set()
242 for f in self._manifest:
243 for f in self._manifest:
243 pos = f.rfind('/')
244 pos = f.rfind('/')
244 while pos != -1:
245 while pos != -1:
245 f = f[:pos]
246 f = f[:pos]
246 if f in dirs:
247 if f in dirs:
247 break # dirs already contains this and above
248 break # dirs already contains this and above
248 dirs.add(f)
249 dirs.add(f)
249 pos = f.rfind('/')
250 pos = f.rfind('/')
250 return dirs
251 return dirs
251
252
252 def dirs(self):
253 def dirs(self):
253 return self._dirs
254 return self._dirs
254
255
255 class filectx(object):
256 class filectx(object):
256 """A filecontext object makes access to data related to a particular
257 """A filecontext object makes access to data related to a particular
257 filerevision convenient."""
258 filerevision convenient."""
258 def __init__(self, repo, path, changeid=None, fileid=None,
259 def __init__(self, repo, path, changeid=None, fileid=None,
259 filelog=None, changectx=None):
260 filelog=None, changectx=None):
260 """changeid can be a changeset revision, node, or tag.
261 """changeid can be a changeset revision, node, or tag.
261 fileid can be a file revision or node."""
262 fileid can be a file revision or node."""
262 self._repo = repo
263 self._repo = repo
263 self._path = path
264 self._path = path
264
265
265 assert (changeid is not None
266 assert (changeid is not None
266 or fileid is not None
267 or fileid is not None
267 or changectx is not None), \
268 or changectx is not None), \
268 ("bad args: changeid=%r, fileid=%r, changectx=%r"
269 ("bad args: changeid=%r, fileid=%r, changectx=%r"
269 % (changeid, fileid, changectx))
270 % (changeid, fileid, changectx))
270
271
271 if filelog:
272 if filelog:
272 self._filelog = filelog
273 self._filelog = filelog
273
274
274 if changeid is not None:
275 if changeid is not None:
275 self._changeid = changeid
276 self._changeid = changeid
276 if changectx is not None:
277 if changectx is not None:
277 self._changectx = changectx
278 self._changectx = changectx
278 if fileid is not None:
279 if fileid is not None:
279 self._fileid = fileid
280 self._fileid = fileid
280
281
281 @propertycache
282 @propertycache
282 def _changectx(self):
283 def _changectx(self):
283 return changectx(self._repo, self._changeid)
284 return changectx(self._repo, self._changeid)
284
285
285 @propertycache
286 @propertycache
286 def _filelog(self):
287 def _filelog(self):
287 return self._repo.file(self._path)
288 return self._repo.file(self._path)
288
289
289 @propertycache
290 @propertycache
290 def _changeid(self):
291 def _changeid(self):
291 if '_changectx' in self.__dict__:
292 if '_changectx' in self.__dict__:
292 return self._changectx.rev()
293 return self._changectx.rev()
293 else:
294 else:
294 return self._filelog.linkrev(self._filerev)
295 return self._filelog.linkrev(self._filerev)
295
296
296 @propertycache
297 @propertycache
297 def _filenode(self):
298 def _filenode(self):
298 if '_fileid' in self.__dict__:
299 if '_fileid' in self.__dict__:
299 return self._filelog.lookup(self._fileid)
300 return self._filelog.lookup(self._fileid)
300 else:
301 else:
301 return self._changectx.filenode(self._path)
302 return self._changectx.filenode(self._path)
302
303
303 @propertycache
304 @propertycache
304 def _filerev(self):
305 def _filerev(self):
305 return self._filelog.rev(self._filenode)
306 return self._filelog.rev(self._filenode)
306
307
307 @propertycache
308 @propertycache
308 def _repopath(self):
309 def _repopath(self):
309 return self._path
310 return self._path
310
311
311 def __nonzero__(self):
312 def __nonzero__(self):
312 try:
313 try:
313 self._filenode
314 self._filenode
314 return True
315 return True
315 except error.LookupError:
316 except error.LookupError:
316 # file is missing
317 # file is missing
317 return False
318 return False
318
319
319 def __str__(self):
320 def __str__(self):
320 return "%s@%s" % (self.path(), short(self.node()))
321 return "%s@%s" % (self.path(), short(self.node()))
321
322
322 def __repr__(self):
323 def __repr__(self):
323 return "<filectx %s>" % str(self)
324 return "<filectx %s>" % str(self)
324
325
325 def __hash__(self):
326 def __hash__(self):
326 try:
327 try:
327 return hash((self._path, self._filenode))
328 return hash((self._path, self._filenode))
328 except AttributeError:
329 except AttributeError:
329 return id(self)
330 return id(self)
330
331
331 def __eq__(self, other):
332 def __eq__(self, other):
332 try:
333 try:
333 return (self._path == other._path
334 return (self._path == other._path
334 and self._filenode == other._filenode)
335 and self._filenode == other._filenode)
335 except AttributeError:
336 except AttributeError:
336 return False
337 return False
337
338
338 def __ne__(self, other):
339 def __ne__(self, other):
339 return not (self == other)
340 return not (self == other)
340
341
341 def filectx(self, fileid):
342 def filectx(self, fileid):
342 '''opens an arbitrary revision of the file without
343 '''opens an arbitrary revision of the file without
343 opening a new filelog'''
344 opening a new filelog'''
344 return filectx(self._repo, self._path, fileid=fileid,
345 return filectx(self._repo, self._path, fileid=fileid,
345 filelog=self._filelog)
346 filelog=self._filelog)
346
347
347 def filerev(self):
348 def filerev(self):
348 return self._filerev
349 return self._filerev
349 def filenode(self):
350 def filenode(self):
350 return self._filenode
351 return self._filenode
351 def flags(self):
352 def flags(self):
352 return self._changectx.flags(self._path)
353 return self._changectx.flags(self._path)
353 def filelog(self):
354 def filelog(self):
354 return self._filelog
355 return self._filelog
355
356
356 def rev(self):
357 def rev(self):
357 if '_changectx' in self.__dict__:
358 if '_changectx' in self.__dict__:
358 return self._changectx.rev()
359 return self._changectx.rev()
359 if '_changeid' in self.__dict__:
360 if '_changeid' in self.__dict__:
360 return self._changectx.rev()
361 return self._changectx.rev()
361 return self._filelog.linkrev(self._filerev)
362 return self._filelog.linkrev(self._filerev)
362
363
363 def linkrev(self):
364 def linkrev(self):
364 return self._filelog.linkrev(self._filerev)
365 return self._filelog.linkrev(self._filerev)
365 def node(self):
366 def node(self):
366 return self._changectx.node()
367 return self._changectx.node()
367 def hex(self):
368 def hex(self):
368 return hex(self.node())
369 return hex(self.node())
369 def user(self):
370 def user(self):
370 return self._changectx.user()
371 return self._changectx.user()
371 def date(self):
372 def date(self):
372 return self._changectx.date()
373 return self._changectx.date()
373 def files(self):
374 def files(self):
374 return self._changectx.files()
375 return self._changectx.files()
375 def description(self):
376 def description(self):
376 return self._changectx.description()
377 return self._changectx.description()
377 def branch(self):
378 def branch(self):
378 return self._changectx.branch()
379 return self._changectx.branch()
379 def extra(self):
380 def extra(self):
380 return self._changectx.extra()
381 return self._changectx.extra()
381 def manifest(self):
382 def manifest(self):
382 return self._changectx.manifest()
383 return self._changectx.manifest()
383 def changectx(self):
384 def changectx(self):
384 return self._changectx
385 return self._changectx
385
386
386 def data(self):
387 def data(self):
387 return self._filelog.read(self._filenode)
388 return self._filelog.read(self._filenode)
388 def path(self):
389 def path(self):
389 return self._path
390 return self._path
390 def size(self):
391 def size(self):
391 return self._filelog.size(self._filerev)
392 return self._filelog.size(self._filerev)
392
393
393 def isbinary(self):
394 def isbinary(self):
394 try:
395 try:
395 return util.binary(self.data())
396 return util.binary(self.data())
396 except IOError:
397 except IOError:
397 return False
398 return False
398
399
399 def cmp(self, fctx):
400 def cmp(self, fctx):
400 """compare with other file context
401 """compare with other file context
401
402
402 returns True if different than fctx.
403 returns True if different than fctx.
403 """
404 """
404 if (fctx._filerev is None
405 if (fctx._filerev is None
405 and (self._repo._encodefilterpats
406 and (self._repo._encodefilterpats
406 # if file data starts with '\1\n', empty metadata block is
407 # if file data starts with '\1\n', empty metadata block is
407 # prepended, which adds 4 bytes to filelog.size().
408 # prepended, which adds 4 bytes to filelog.size().
408 or self.size() - 4 == fctx.size())
409 or self.size() - 4 == fctx.size())
409 or self.size() == fctx.size()):
410 or self.size() == fctx.size()):
410 return self._filelog.cmp(self._filenode, fctx.data())
411 return self._filelog.cmp(self._filenode, fctx.data())
411
412
412 return True
413 return True
413
414
414 def renamed(self):
415 def renamed(self):
415 """check if file was actually renamed in this changeset revision
416 """check if file was actually renamed in this changeset revision
416
417
417 If rename logged in file revision, we report copy for changeset only
418 If rename logged in file revision, we report copy for changeset only
418 if file revisions linkrev points back to the changeset in question
419 if file revisions linkrev points back to the changeset in question
419 or both changeset parents contain different file revisions.
420 or both changeset parents contain different file revisions.
420 """
421 """
421
422
422 renamed = self._filelog.renamed(self._filenode)
423 renamed = self._filelog.renamed(self._filenode)
423 if not renamed:
424 if not renamed:
424 return renamed
425 return renamed
425
426
426 if self.rev() == self.linkrev():
427 if self.rev() == self.linkrev():
427 return renamed
428 return renamed
428
429
429 name = self.path()
430 name = self.path()
430 fnode = self._filenode
431 fnode = self._filenode
431 for p in self._changectx.parents():
432 for p in self._changectx.parents():
432 try:
433 try:
433 if fnode == p.filenode(name):
434 if fnode == p.filenode(name):
434 return None
435 return None
435 except error.LookupError:
436 except error.LookupError:
436 pass
437 pass
437 return renamed
438 return renamed
438
439
439 def parents(self):
440 def parents(self):
440 p = self._path
441 p = self._path
441 fl = self._filelog
442 fl = self._filelog
442 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
443 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
443
444
444 r = self._filelog.renamed(self._filenode)
445 r = self._filelog.renamed(self._filenode)
445 if r:
446 if r:
446 pl[0] = (r[0], r[1], None)
447 pl[0] = (r[0], r[1], None)
447
448
448 return [filectx(self._repo, p, fileid=n, filelog=l)
449 return [filectx(self._repo, p, fileid=n, filelog=l)
449 for p, n, l in pl if n != nullid]
450 for p, n, l in pl if n != nullid]
450
451
451 def p1(self):
452 def p1(self):
452 return self.parents()[0]
453 return self.parents()[0]
453
454
454 def p2(self):
455 def p2(self):
455 p = self.parents()
456 p = self.parents()
456 if len(p) == 2:
457 if len(p) == 2:
457 return p[1]
458 return p[1]
458 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
459 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
459
460
460 def children(self):
461 def children(self):
461 # hard for renames
462 # hard for renames
462 c = self._filelog.children(self._filenode)
463 c = self._filelog.children(self._filenode)
463 return [filectx(self._repo, self._path, fileid=x,
464 return [filectx(self._repo, self._path, fileid=x,
464 filelog=self._filelog) for x in c]
465 filelog=self._filelog) for x in c]
465
466
466 def annotate(self, follow=False, linenumber=None, diffopts=None):
467 def annotate(self, follow=False, linenumber=None, diffopts=None):
467 '''returns a list of tuples of (ctx, line) for each line
468 '''returns a list of tuples of (ctx, line) for each line
468 in the file, where ctx is the filectx of the node where
469 in the file, where ctx is the filectx of the node where
469 that line was last changed.
470 that line was last changed.
470 This returns tuples of ((ctx, linenumber), line) for each line,
471 This returns tuples of ((ctx, linenumber), line) for each line,
471 if "linenumber" parameter is NOT "None".
472 if "linenumber" parameter is NOT "None".
472 In such tuples, linenumber means one at the first appearance
473 In such tuples, linenumber means one at the first appearance
473 in the managed file.
474 in the managed file.
474 To reduce annotation cost,
475 To reduce annotation cost,
475 this returns fixed value(False is used) as linenumber,
476 this returns fixed value(False is used) as linenumber,
476 if "linenumber" parameter is "False".'''
477 if "linenumber" parameter is "False".'''
477
478
478 def decorate_compat(text, rev):
479 def decorate_compat(text, rev):
479 return ([rev] * len(text.splitlines()), text)
480 return ([rev] * len(text.splitlines()), text)
480
481
481 def without_linenumber(text, rev):
482 def without_linenumber(text, rev):
482 return ([(rev, False)] * len(text.splitlines()), text)
483 return ([(rev, False)] * len(text.splitlines()), text)
483
484
484 def with_linenumber(text, rev):
485 def with_linenumber(text, rev):
485 size = len(text.splitlines())
486 size = len(text.splitlines())
486 return ([(rev, i) for i in xrange(1, size + 1)], text)
487 return ([(rev, i) for i in xrange(1, size + 1)], text)
487
488
488 decorate = (((linenumber is None) and decorate_compat) or
489 decorate = (((linenumber is None) and decorate_compat) or
489 (linenumber and with_linenumber) or
490 (linenumber and with_linenumber) or
490 without_linenumber)
491 without_linenumber)
491
492
492 def pair(parent, child):
493 def pair(parent, child):
493 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
494 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
494 refine=True)
495 refine=True)
495 for (a1, a2, b1, b2), t in blocks:
496 for (a1, a2, b1, b2), t in blocks:
496 # Changed blocks ('!') or blocks made only of blank lines ('~')
497 # Changed blocks ('!') or blocks made only of blank lines ('~')
497 # belong to the child.
498 # belong to the child.
498 if t == '=':
499 if t == '=':
499 child[0][b1:b2] = parent[0][a1:a2]
500 child[0][b1:b2] = parent[0][a1:a2]
500 return child
501 return child
501
502
502 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
503 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
503 def getctx(path, fileid):
504 def getctx(path, fileid):
504 log = path == self._path and self._filelog or getlog(path)
505 log = path == self._path and self._filelog or getlog(path)
505 return filectx(self._repo, path, fileid=fileid, filelog=log)
506 return filectx(self._repo, path, fileid=fileid, filelog=log)
506 getctx = util.lrucachefunc(getctx)
507 getctx = util.lrucachefunc(getctx)
507
508
508 def parents(f):
509 def parents(f):
509 # we want to reuse filectx objects as much as possible
510 # we want to reuse filectx objects as much as possible
510 p = f._path
511 p = f._path
511 if f._filerev is None: # working dir
512 if f._filerev is None: # working dir
512 pl = [(n.path(), n.filerev()) for n in f.parents()]
513 pl = [(n.path(), n.filerev()) for n in f.parents()]
513 else:
514 else:
514 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
515 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
515
516
516 if follow:
517 if follow:
517 r = f.renamed()
518 r = f.renamed()
518 if r:
519 if r:
519 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
520 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
520
521
521 return [getctx(p, n) for p, n in pl if n != nullrev]
522 return [getctx(p, n) for p, n in pl if n != nullrev]
522
523
523 # use linkrev to find the first changeset where self appeared
524 # use linkrev to find the first changeset where self appeared
524 if self.rev() != self.linkrev():
525 if self.rev() != self.linkrev():
525 base = self.filectx(self.filerev())
526 base = self.filectx(self.filerev())
526 else:
527 else:
527 base = self
528 base = self
528
529
529 # This algorithm would prefer to be recursive, but Python is a
530 # This algorithm would prefer to be recursive, but Python is a
530 # bit recursion-hostile. Instead we do an iterative
531 # bit recursion-hostile. Instead we do an iterative
531 # depth-first search.
532 # depth-first search.
532
533
533 visit = [base]
534 visit = [base]
534 hist = {}
535 hist = {}
535 pcache = {}
536 pcache = {}
536 needed = {base: 1}
537 needed = {base: 1}
537 while visit:
538 while visit:
538 f = visit[-1]
539 f = visit[-1]
539 if f not in pcache:
540 if f not in pcache:
540 pcache[f] = parents(f)
541 pcache[f] = parents(f)
541
542
542 ready = True
543 ready = True
543 pl = pcache[f]
544 pl = pcache[f]
544 for p in pl:
545 for p in pl:
545 if p not in hist:
546 if p not in hist:
546 ready = False
547 ready = False
547 visit.append(p)
548 visit.append(p)
548 needed[p] = needed.get(p, 0) + 1
549 needed[p] = needed.get(p, 0) + 1
549 if ready:
550 if ready:
550 visit.pop()
551 visit.pop()
551 curr = decorate(f.data(), f)
552 curr = decorate(f.data(), f)
552 for p in pl:
553 for p in pl:
553 curr = pair(hist[p], curr)
554 curr = pair(hist[p], curr)
554 if needed[p] == 1:
555 if needed[p] == 1:
555 del hist[p]
556 del hist[p]
556 else:
557 else:
557 needed[p] -= 1
558 needed[p] -= 1
558
559
559 hist[f] = curr
560 hist[f] = curr
560 pcache[f] = []
561 pcache[f] = []
561
562
562 return zip(hist[base][0], hist[base][1].splitlines(True))
563 return zip(hist[base][0], hist[base][1].splitlines(True))
563
564
564 def ancestor(self, fc2, actx=None):
565 def ancestor(self, fc2, actx=None):
565 """
566 """
566 find the common ancestor file context, if any, of self, and fc2
567 find the common ancestor file context, if any, of self, and fc2
567
568
568 If actx is given, it must be the changectx of the common ancestor
569 If actx is given, it must be the changectx of the common ancestor
569 of self's and fc2's respective changesets.
570 of self's and fc2's respective changesets.
570 """
571 """
571
572
572 if actx is None:
573 if actx is None:
573 actx = self.changectx().ancestor(fc2.changectx())
574 actx = self.changectx().ancestor(fc2.changectx())
574
575
575 # the trivial case: changesets are unrelated, files must be too
576 # the trivial case: changesets are unrelated, files must be too
576 if not actx:
577 if not actx:
577 return None
578 return None
578
579
579 # the easy case: no (relevant) renames
580 # the easy case: no (relevant) renames
580 if fc2.path() == self.path() and self.path() in actx:
581 if fc2.path() == self.path() and self.path() in actx:
581 return actx[self.path()]
582 return actx[self.path()]
582 acache = {}
583 acache = {}
583
584
584 # prime the ancestor cache for the working directory
585 # prime the ancestor cache for the working directory
585 for c in (self, fc2):
586 for c in (self, fc2):
586 if c._filerev is None:
587 if c._filerev is None:
587 pl = [(n.path(), n.filenode()) for n in c.parents()]
588 pl = [(n.path(), n.filenode()) for n in c.parents()]
588 acache[(c._path, None)] = pl
589 acache[(c._path, None)] = pl
589
590
590 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
591 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
591 def parents(vertex):
592 def parents(vertex):
592 if vertex in acache:
593 if vertex in acache:
593 return acache[vertex]
594 return acache[vertex]
594 f, n = vertex
595 f, n = vertex
595 if f not in flcache:
596 if f not in flcache:
596 flcache[f] = self._repo.file(f)
597 flcache[f] = self._repo.file(f)
597 fl = flcache[f]
598 fl = flcache[f]
598 pl = [(f, p) for p in fl.parents(n) if p != nullid]
599 pl = [(f, p) for p in fl.parents(n) if p != nullid]
599 re = fl.renamed(n)
600 re = fl.renamed(n)
600 if re:
601 if re:
601 pl.append(re)
602 pl.append(re)
602 acache[vertex] = pl
603 acache[vertex] = pl
603 return pl
604 return pl
604
605
605 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
606 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
606 v = ancestor.ancestor(a, b, parents)
607 v = ancestor.ancestor(a, b, parents)
607 if v:
608 if v:
608 f, n = v
609 f, n = v
609 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
610 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
610
611
611 return None
612 return None
612
613
613 def ancestors(self):
614 def ancestors(self):
614 visit = {}
615 visit = {}
615 c = self
616 c = self
616 while True:
617 while True:
617 for parent in c.parents():
618 for parent in c.parents():
618 visit[(parent.rev(), parent.node())] = parent
619 visit[(parent.rev(), parent.node())] = parent
619 if not visit:
620 if not visit:
620 break
621 break
621 c = visit.pop(max(visit))
622 c = visit.pop(max(visit))
622 yield c
623 yield c
623
624
624 class workingctx(changectx):
625 class workingctx(changectx):
625 """A workingctx object makes access to data related to
626 """A workingctx object makes access to data related to
626 the current working directory convenient.
627 the current working directory convenient.
627 date - any valid date string or (unixtime, offset), or None.
628 date - any valid date string or (unixtime, offset), or None.
628 user - username string, or None.
629 user - username string, or None.
629 extra - a dictionary of extra values, or None.
630 extra - a dictionary of extra values, or None.
630 changes - a list of file lists as returned by localrepo.status()
631 changes - a list of file lists as returned by localrepo.status()
631 or None to use the repository status.
632 or None to use the repository status.
632 """
633 """
633 def __init__(self, repo, text="", user=None, date=None, extra=None,
634 def __init__(self, repo, text="", user=None, date=None, extra=None,
634 changes=None):
635 changes=None):
635 self._repo = repo
636 self._repo = repo
636 self._rev = None
637 self._rev = None
637 self._node = None
638 self._node = None
638 self._text = text
639 self._text = text
639 if date:
640 if date:
640 self._date = util.parsedate(date)
641 self._date = util.parsedate(date)
641 if user:
642 if user:
642 self._user = user
643 self._user = user
643 if changes:
644 if changes:
644 self._status = list(changes[:4])
645 self._status = list(changes[:4])
645 self._unknown = changes[4]
646 self._unknown = changes[4]
646 self._ignored = changes[5]
647 self._ignored = changes[5]
647 self._clean = changes[6]
648 self._clean = changes[6]
648 else:
649 else:
649 self._unknown = None
650 self._unknown = None
650 self._ignored = None
651 self._ignored = None
651 self._clean = None
652 self._clean = None
652
653
653 self._extra = {}
654 self._extra = {}
654 if extra:
655 if extra:
655 self._extra = extra.copy()
656 self._extra = extra.copy()
656 if 'branch' not in self._extra:
657 if 'branch' not in self._extra:
657 try:
658 try:
658 branch = encoding.fromlocal(self._repo.dirstate.branch())
659 branch = encoding.fromlocal(self._repo.dirstate.branch())
659 except UnicodeDecodeError:
660 except UnicodeDecodeError:
660 raise util.Abort(_('branch name not in UTF-8!'))
661 raise util.Abort(_('branch name not in UTF-8!'))
661 self._extra['branch'] = branch
662 self._extra['branch'] = branch
662 if self._extra['branch'] == '':
663 if self._extra['branch'] == '':
663 self._extra['branch'] = 'default'
664 self._extra['branch'] = 'default'
664
665
665 def __str__(self):
666 def __str__(self):
666 return str(self._parents[0]) + "+"
667 return str(self._parents[0]) + "+"
667
668
668 def __repr__(self):
669 def __repr__(self):
669 return "<workingctx %s>" % str(self)
670 return "<workingctx %s>" % str(self)
670
671
671 def __nonzero__(self):
672 def __nonzero__(self):
672 return True
673 return True
673
674
674 def __contains__(self, key):
675 def __contains__(self, key):
675 return self._repo.dirstate[key] not in "?r"
676 return self._repo.dirstate[key] not in "?r"
676
677
677 def _buildflagfunc(self):
678 def _buildflagfunc(self):
678 # Create a fallback function for getting file flags when the
679 # Create a fallback function for getting file flags when the
679 # filesystem doesn't support them
680 # filesystem doesn't support them
680
681
681 copiesget = self._repo.dirstate.copies().get
682 copiesget = self._repo.dirstate.copies().get
682
683
683 if len(self._parents) < 2:
684 if len(self._parents) < 2:
684 # when we have one parent, it's easy: copy from parent
685 # when we have one parent, it's easy: copy from parent
685 man = self._parents[0].manifest()
686 man = self._parents[0].manifest()
686 def func(f):
687 def func(f):
687 f = copiesget(f, f)
688 f = copiesget(f, f)
688 return man.flags(f)
689 return man.flags(f)
689 else:
690 else:
690 # merges are tricky: we try to reconstruct the unstored
691 # merges are tricky: we try to reconstruct the unstored
691 # result from the merge (issue1802)
692 # result from the merge (issue1802)
692 p1, p2 = self._parents
693 p1, p2 = self._parents
693 pa = p1.ancestor(p2)
694 pa = p1.ancestor(p2)
694 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
695 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
695
696
696 def func(f):
697 def func(f):
697 f = copiesget(f, f) # may be wrong for merges with copies
698 f = copiesget(f, f) # may be wrong for merges with copies
698 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
699 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
699 if fl1 == fl2:
700 if fl1 == fl2:
700 return fl1
701 return fl1
701 if fl1 == fla:
702 if fl1 == fla:
702 return fl2
703 return fl2
703 if fl2 == fla:
704 if fl2 == fla:
704 return fl1
705 return fl1
705 return '' # punt for conflicts
706 return '' # punt for conflicts
706
707
707 return func
708 return func
708
709
709 @propertycache
710 @propertycache
710 def _flagfunc(self):
711 def _flagfunc(self):
711 return self._repo.dirstate.flagfunc(self._buildflagfunc)
712 return self._repo.dirstate.flagfunc(self._buildflagfunc)
712
713
713 @propertycache
714 @propertycache
714 def _manifest(self):
715 def _manifest(self):
715 """generate a manifest corresponding to the working directory"""
716 """generate a manifest corresponding to the working directory"""
716
717
717 if self._unknown is None:
718 if self._unknown is None:
718 self.status(unknown=True)
719 self.status(unknown=True)
719
720
720 man = self._parents[0].manifest().copy()
721 man = self._parents[0].manifest().copy()
721 if len(self._parents) > 1:
722 if len(self._parents) > 1:
722 man2 = self.p2().manifest()
723 man2 = self.p2().manifest()
723 def getman(f):
724 def getman(f):
724 if f in man:
725 if f in man:
725 return man
726 return man
726 return man2
727 return man2
727 else:
728 else:
728 getman = lambda f: man
729 getman = lambda f: man
729
730
730 copied = self._repo.dirstate.copies()
731 copied = self._repo.dirstate.copies()
731 ff = self._flagfunc
732 ff = self._flagfunc
732 modified, added, removed, deleted = self._status
733 modified, added, removed, deleted = self._status
733 unknown = self._unknown
734 unknown = self._unknown
734 for i, l in (("a", added), ("m", modified), ("u", unknown)):
735 for i, l in (("a", added), ("m", modified), ("u", unknown)):
735 for f in l:
736 for f in l:
736 orig = copied.get(f, f)
737 orig = copied.get(f, f)
737 man[f] = getman(orig).get(orig, nullid) + i
738 man[f] = getman(orig).get(orig, nullid) + i
738 try:
739 try:
739 man.set(f, ff(f))
740 man.set(f, ff(f))
740 except OSError:
741 except OSError:
741 pass
742 pass
742
743
743 for f in deleted + removed:
744 for f in deleted + removed:
744 if f in man:
745 if f in man:
745 del man[f]
746 del man[f]
746
747
747 return man
748 return man
748
749
749 def __iter__(self):
750 def __iter__(self):
750 d = self._repo.dirstate
751 d = self._repo.dirstate
751 for f in d:
752 for f in d:
752 if d[f] != 'r':
753 if d[f] != 'r':
753 yield f
754 yield f
754
755
755 @propertycache
756 @propertycache
756 def _status(self):
757 def _status(self):
757 return self._repo.status()[:4]
758 return self._repo.status()[:4]
758
759
759 @propertycache
760 @propertycache
760 def _user(self):
761 def _user(self):
761 return self._repo.ui.username()
762 return self._repo.ui.username()
762
763
763 @propertycache
764 @propertycache
764 def _date(self):
765 def _date(self):
765 return util.makedate()
766 return util.makedate()
766
767
767 @propertycache
768 @propertycache
768 def _parents(self):
769 def _parents(self):
769 p = self._repo.dirstate.parents()
770 p = self._repo.dirstate.parents()
770 if p[1] == nullid:
771 if p[1] == nullid:
771 p = p[:-1]
772 p = p[:-1]
772 self._parents = [changectx(self._repo, x) for x in p]
773 self._parents = [changectx(self._repo, x) for x in p]
773 return self._parents
774 return self._parents
774
775
775 def status(self, ignored=False, clean=False, unknown=False):
776 def status(self, ignored=False, clean=False, unknown=False):
776 """Explicit status query
777 """Explicit status query
777 Unless this method is used to query the working copy status, the
778 Unless this method is used to query the working copy status, the
778 _status property will implicitly read the status using its default
779 _status property will implicitly read the status using its default
779 arguments."""
780 arguments."""
780 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
781 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
781 self._unknown = self._ignored = self._clean = None
782 self._unknown = self._ignored = self._clean = None
782 if unknown:
783 if unknown:
783 self._unknown = stat[4]
784 self._unknown = stat[4]
784 if ignored:
785 if ignored:
785 self._ignored = stat[5]
786 self._ignored = stat[5]
786 if clean:
787 if clean:
787 self._clean = stat[6]
788 self._clean = stat[6]
788 self._status = stat[:4]
789 self._status = stat[:4]
789 return stat
790 return stat
790
791
791 def manifest(self):
792 def manifest(self):
792 return self._manifest
793 return self._manifest
793 def user(self):
794 def user(self):
794 return self._user or self._repo.ui.username()
795 return self._user or self._repo.ui.username()
795 def date(self):
796 def date(self):
796 return self._date
797 return self._date
797 def description(self):
798 def description(self):
798 return self._text
799 return self._text
799 def files(self):
800 def files(self):
800 return sorted(self._status[0] + self._status[1] + self._status[2])
801 return sorted(self._status[0] + self._status[1] + self._status[2])
801
802
802 def modified(self):
803 def modified(self):
803 return self._status[0]
804 return self._status[0]
804 def added(self):
805 def added(self):
805 return self._status[1]
806 return self._status[1]
806 def removed(self):
807 def removed(self):
807 return self._status[2]
808 return self._status[2]
808 def deleted(self):
809 def deleted(self):
809 return self._status[3]
810 return self._status[3]
810 def unknown(self):
811 def unknown(self):
811 assert self._unknown is not None # must call status first
812 assert self._unknown is not None # must call status first
812 return self._unknown
813 return self._unknown
813 def ignored(self):
814 def ignored(self):
814 assert self._ignored is not None # must call status first
815 assert self._ignored is not None # must call status first
815 return self._ignored
816 return self._ignored
816 def clean(self):
817 def clean(self):
817 assert self._clean is not None # must call status first
818 assert self._clean is not None # must call status first
818 return self._clean
819 return self._clean
819 def branch(self):
820 def branch(self):
820 return encoding.tolocal(self._extra['branch'])
821 return encoding.tolocal(self._extra['branch'])
821 def extra(self):
822 def extra(self):
822 return self._extra
823 return self._extra
823
824
824 def tags(self):
825 def tags(self):
825 t = []
826 t = []
826 for p in self.parents():
827 for p in self.parents():
827 t.extend(p.tags())
828 t.extend(p.tags())
828 return t
829 return t
829
830
830 def bookmarks(self):
831 def bookmarks(self):
831 b = []
832 b = []
832 for p in self.parents():
833 for p in self.parents():
833 b.extend(p.bookmarks())
834 b.extend(p.bookmarks())
834 return b
835 return b
835
836
836 def phase(self):
837 def phase(self):
837 phase = phases.draft # default phase to draft
838 phase = phases.draft # default phase to draft
838 for p in self.parents():
839 for p in self.parents():
839 phase = max(phase, p.phase())
840 phase = max(phase, p.phase())
840 return phase
841 return phase
841
842
842 def hidden(self):
843 def hidden(self):
843 return False
844 return False
844
845
845 def children(self):
846 def children(self):
846 return []
847 return []
847
848
848 def flags(self, path):
849 def flags(self, path):
849 if '_manifest' in self.__dict__:
850 if '_manifest' in self.__dict__:
850 try:
851 try:
851 return self._manifest.flags(path)
852 return self._manifest.flags(path)
852 except KeyError:
853 except KeyError:
853 return ''
854 return ''
854
855
855 try:
856 try:
856 return self._flagfunc(path)
857 return self._flagfunc(path)
857 except OSError:
858 except OSError:
858 return ''
859 return ''
859
860
860 def filectx(self, path, filelog=None):
861 def filectx(self, path, filelog=None):
861 """get a file context from the working directory"""
862 """get a file context from the working directory"""
862 return workingfilectx(self._repo, path, workingctx=self,
863 return workingfilectx(self._repo, path, workingctx=self,
863 filelog=filelog)
864 filelog=filelog)
864
865
865 def ancestor(self, c2):
866 def ancestor(self, c2):
866 """return the ancestor context of self and c2"""
867 """return the ancestor context of self and c2"""
867 return self._parents[0].ancestor(c2) # punt on two parents for now
868 return self._parents[0].ancestor(c2) # punt on two parents for now
868
869
869 def walk(self, match):
870 def walk(self, match):
870 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
871 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
871 True, False))
872 True, False))
872
873
873 def dirty(self, missing=False):
874 def dirty(self, missing=False):
874 "check whether a working directory is modified"
875 "check whether a working directory is modified"
875 # check subrepos first
876 # check subrepos first
876 for s in self.substate:
877 for s in self.substate:
877 if self.sub(s).dirty():
878 if self.sub(s).dirty():
878 return True
879 return True
879 # check current working dir
880 # check current working dir
880 return (self.p2() or self.branch() != self.p1().branch() or
881 return (self.p2() or self.branch() != self.p1().branch() or
881 self.modified() or self.added() or self.removed() or
882 self.modified() or self.added() or self.removed() or
882 (missing and self.deleted()))
883 (missing and self.deleted()))
883
884
884 def add(self, list, prefix=""):
885 def add(self, list, prefix=""):
885 join = lambda f: os.path.join(prefix, f)
886 join = lambda f: os.path.join(prefix, f)
886 wlock = self._repo.wlock()
887 wlock = self._repo.wlock()
887 ui, ds = self._repo.ui, self._repo.dirstate
888 ui, ds = self._repo.ui, self._repo.dirstate
888 try:
889 try:
889 rejected = []
890 rejected = []
890 for f in list:
891 for f in list:
891 scmutil.checkportable(ui, join(f))
892 scmutil.checkportable(ui, join(f))
892 p = self._repo.wjoin(f)
893 p = self._repo.wjoin(f)
893 try:
894 try:
894 st = os.lstat(p)
895 st = os.lstat(p)
895 except OSError:
896 except OSError:
896 ui.warn(_("%s does not exist!\n") % join(f))
897 ui.warn(_("%s does not exist!\n") % join(f))
897 rejected.append(f)
898 rejected.append(f)
898 continue
899 continue
899 if st.st_size > 10000000:
900 if st.st_size > 10000000:
900 ui.warn(_("%s: up to %d MB of RAM may be required "
901 ui.warn(_("%s: up to %d MB of RAM may be required "
901 "to manage this file\n"
902 "to manage this file\n"
902 "(use 'hg revert %s' to cancel the "
903 "(use 'hg revert %s' to cancel the "
903 "pending addition)\n")
904 "pending addition)\n")
904 % (f, 3 * st.st_size // 1000000, join(f)))
905 % (f, 3 * st.st_size // 1000000, join(f)))
905 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
906 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
906 ui.warn(_("%s not added: only files and symlinks "
907 ui.warn(_("%s not added: only files and symlinks "
907 "supported currently\n") % join(f))
908 "supported currently\n") % join(f))
908 rejected.append(p)
909 rejected.append(p)
909 elif ds[f] in 'amn':
910 elif ds[f] in 'amn':
910 ui.warn(_("%s already tracked!\n") % join(f))
911 ui.warn(_("%s already tracked!\n") % join(f))
911 elif ds[f] == 'r':
912 elif ds[f] == 'r':
912 ds.normallookup(f)
913 ds.normallookup(f)
913 else:
914 else:
914 ds.add(f)
915 ds.add(f)
915 return rejected
916 return rejected
916 finally:
917 finally:
917 wlock.release()
918 wlock.release()
918
919
919 def forget(self, files, prefix=""):
920 def forget(self, files, prefix=""):
920 join = lambda f: os.path.join(prefix, f)
921 join = lambda f: os.path.join(prefix, f)
921 wlock = self._repo.wlock()
922 wlock = self._repo.wlock()
922 try:
923 try:
923 rejected = []
924 rejected = []
924 for f in files:
925 for f in files:
925 if f not in self._repo.dirstate:
926 if f not in self._repo.dirstate:
926 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
927 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
927 rejected.append(f)
928 rejected.append(f)
928 elif self._repo.dirstate[f] != 'a':
929 elif self._repo.dirstate[f] != 'a':
929 self._repo.dirstate.remove(f)
930 self._repo.dirstate.remove(f)
930 else:
931 else:
931 self._repo.dirstate.drop(f)
932 self._repo.dirstate.drop(f)
932 return rejected
933 return rejected
933 finally:
934 finally:
934 wlock.release()
935 wlock.release()
935
936
936 def ancestors(self):
937 def ancestors(self):
937 for a in self._repo.changelog.ancestors(
938 for a in self._repo.changelog.ancestors(
938 *[p.rev() for p in self._parents]):
939 *[p.rev() for p in self._parents]):
939 yield changectx(self._repo, a)
940 yield changectx(self._repo, a)
940
941
941 def undelete(self, list):
942 def undelete(self, list):
942 pctxs = self.parents()
943 pctxs = self.parents()
943 wlock = self._repo.wlock()
944 wlock = self._repo.wlock()
944 try:
945 try:
945 for f in list:
946 for f in list:
946 if self._repo.dirstate[f] != 'r':
947 if self._repo.dirstate[f] != 'r':
947 self._repo.ui.warn(_("%s not removed!\n") % f)
948 self._repo.ui.warn(_("%s not removed!\n") % f)
948 else:
949 else:
949 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
950 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
950 t = fctx.data()
951 t = fctx.data()
951 self._repo.wwrite(f, t, fctx.flags())
952 self._repo.wwrite(f, t, fctx.flags())
952 self._repo.dirstate.normal(f)
953 self._repo.dirstate.normal(f)
953 finally:
954 finally:
954 wlock.release()
955 wlock.release()
955
956
956 def copy(self, source, dest):
957 def copy(self, source, dest):
957 p = self._repo.wjoin(dest)
958 p = self._repo.wjoin(dest)
958 if not os.path.lexists(p):
959 if not os.path.lexists(p):
959 self._repo.ui.warn(_("%s does not exist!\n") % dest)
960 self._repo.ui.warn(_("%s does not exist!\n") % dest)
960 elif not (os.path.isfile(p) or os.path.islink(p)):
961 elif not (os.path.isfile(p) or os.path.islink(p)):
961 self._repo.ui.warn(_("copy failed: %s is not a file or a "
962 self._repo.ui.warn(_("copy failed: %s is not a file or a "
962 "symbolic link\n") % dest)
963 "symbolic link\n") % dest)
963 else:
964 else:
964 wlock = self._repo.wlock()
965 wlock = self._repo.wlock()
965 try:
966 try:
966 if self._repo.dirstate[dest] in '?r':
967 if self._repo.dirstate[dest] in '?r':
967 self._repo.dirstate.add(dest)
968 self._repo.dirstate.add(dest)
968 self._repo.dirstate.copy(source, dest)
969 self._repo.dirstate.copy(source, dest)
969 finally:
970 finally:
970 wlock.release()
971 wlock.release()
971
972
972 def dirs(self):
973 def dirs(self):
973 return self._repo.dirstate.dirs()
974 return self._repo.dirstate.dirs()
974
975
975 class workingfilectx(filectx):
976 class workingfilectx(filectx):
976 """A workingfilectx object makes access to data related to a particular
977 """A workingfilectx object makes access to data related to a particular
977 file in the working directory convenient."""
978 file in the working directory convenient."""
978 def __init__(self, repo, path, filelog=None, workingctx=None):
979 def __init__(self, repo, path, filelog=None, workingctx=None):
979 """changeid can be a changeset revision, node, or tag.
980 """changeid can be a changeset revision, node, or tag.
980 fileid can be a file revision or node."""
981 fileid can be a file revision or node."""
981 self._repo = repo
982 self._repo = repo
982 self._path = path
983 self._path = path
983 self._changeid = None
984 self._changeid = None
984 self._filerev = self._filenode = None
985 self._filerev = self._filenode = None
985
986
986 if filelog:
987 if filelog:
987 self._filelog = filelog
988 self._filelog = filelog
988 if workingctx:
989 if workingctx:
989 self._changectx = workingctx
990 self._changectx = workingctx
990
991
991 @propertycache
992 @propertycache
992 def _changectx(self):
993 def _changectx(self):
993 return workingctx(self._repo)
994 return workingctx(self._repo)
994
995
995 def __nonzero__(self):
996 def __nonzero__(self):
996 return True
997 return True
997
998
998 def __str__(self):
999 def __str__(self):
999 return "%s@%s" % (self.path(), self._changectx)
1000 return "%s@%s" % (self.path(), self._changectx)
1000
1001
1001 def __repr__(self):
1002 def __repr__(self):
1002 return "<workingfilectx %s>" % str(self)
1003 return "<workingfilectx %s>" % str(self)
1003
1004
1004 def data(self):
1005 def data(self):
1005 return self._repo.wread(self._path)
1006 return self._repo.wread(self._path)
1006 def renamed(self):
1007 def renamed(self):
1007 rp = self._repo.dirstate.copied(self._path)
1008 rp = self._repo.dirstate.copied(self._path)
1008 if not rp:
1009 if not rp:
1009 return None
1010 return None
1010 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1011 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1011
1012
1012 def parents(self):
1013 def parents(self):
1013 '''return parent filectxs, following copies if necessary'''
1014 '''return parent filectxs, following copies if necessary'''
1014 def filenode(ctx, path):
1015 def filenode(ctx, path):
1015 return ctx._manifest.get(path, nullid)
1016 return ctx._manifest.get(path, nullid)
1016
1017
1017 path = self._path
1018 path = self._path
1018 fl = self._filelog
1019 fl = self._filelog
1019 pcl = self._changectx._parents
1020 pcl = self._changectx._parents
1020 renamed = self.renamed()
1021 renamed = self.renamed()
1021
1022
1022 if renamed:
1023 if renamed:
1023 pl = [renamed + (None,)]
1024 pl = [renamed + (None,)]
1024 else:
1025 else:
1025 pl = [(path, filenode(pcl[0], path), fl)]
1026 pl = [(path, filenode(pcl[0], path), fl)]
1026
1027
1027 for pc in pcl[1:]:
1028 for pc in pcl[1:]:
1028 pl.append((path, filenode(pc, path), fl))
1029 pl.append((path, filenode(pc, path), fl))
1029
1030
1030 return [filectx(self._repo, p, fileid=n, filelog=l)
1031 return [filectx(self._repo, p, fileid=n, filelog=l)
1031 for p, n, l in pl if n != nullid]
1032 for p, n, l in pl if n != nullid]
1032
1033
1033 def children(self):
1034 def children(self):
1034 return []
1035 return []
1035
1036
1036 def size(self):
1037 def size(self):
1037 return os.lstat(self._repo.wjoin(self._path)).st_size
1038 return os.lstat(self._repo.wjoin(self._path)).st_size
1038 def date(self):
1039 def date(self):
1039 t, tz = self._changectx.date()
1040 t, tz = self._changectx.date()
1040 try:
1041 try:
1041 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1042 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1042 except OSError, err:
1043 except OSError, err:
1043 if err.errno != errno.ENOENT:
1044 if err.errno != errno.ENOENT:
1044 raise
1045 raise
1045 return (t, tz)
1046 return (t, tz)
1046
1047
1047 def cmp(self, fctx):
1048 def cmp(self, fctx):
1048 """compare with other file context
1049 """compare with other file context
1049
1050
1050 returns True if different than fctx.
1051 returns True if different than fctx.
1051 """
1052 """
1052 # fctx should be a filectx (not a wfctx)
1053 # fctx should be a filectx (not a wfctx)
1053 # invert comparison to reuse the same code path
1054 # invert comparison to reuse the same code path
1054 return fctx.cmp(self)
1055 return fctx.cmp(self)
1055
1056
1056 class memctx(object):
1057 class memctx(object):
1057 """Use memctx to perform in-memory commits via localrepo.commitctx().
1058 """Use memctx to perform in-memory commits via localrepo.commitctx().
1058
1059
1059 Revision information is supplied at initialization time while
1060 Revision information is supplied at initialization time while
1060 related files data and is made available through a callback
1061 related files data and is made available through a callback
1061 mechanism. 'repo' is the current localrepo, 'parents' is a
1062 mechanism. 'repo' is the current localrepo, 'parents' is a
1062 sequence of two parent revisions identifiers (pass None for every
1063 sequence of two parent revisions identifiers (pass None for every
1063 missing parent), 'text' is the commit message and 'files' lists
1064 missing parent), 'text' is the commit message and 'files' lists
1064 names of files touched by the revision (normalized and relative to
1065 names of files touched by the revision (normalized and relative to
1065 repository root).
1066 repository root).
1066
1067
1067 filectxfn(repo, memctx, path) is a callable receiving the
1068 filectxfn(repo, memctx, path) is a callable receiving the
1068 repository, the current memctx object and the normalized path of
1069 repository, the current memctx object and the normalized path of
1069 requested file, relative to repository root. It is fired by the
1070 requested file, relative to repository root. It is fired by the
1070 commit function for every file in 'files', but calls order is
1071 commit function for every file in 'files', but calls order is
1071 undefined. If the file is available in the revision being
1072 undefined. If the file is available in the revision being
1072 committed (updated or added), filectxfn returns a memfilectx
1073 committed (updated or added), filectxfn returns a memfilectx
1073 object. If the file was removed, filectxfn raises an
1074 object. If the file was removed, filectxfn raises an
1074 IOError. Moved files are represented by marking the source file
1075 IOError. Moved files are represented by marking the source file
1075 removed and the new file added with copy information (see
1076 removed and the new file added with copy information (see
1076 memfilectx).
1077 memfilectx).
1077
1078
1078 user receives the committer name and defaults to current
1079 user receives the committer name and defaults to current
1079 repository username, date is the commit date in any format
1080 repository username, date is the commit date in any format
1080 supported by util.parsedate() and defaults to current date, extra
1081 supported by util.parsedate() and defaults to current date, extra
1081 is a dictionary of metadata or is left empty.
1082 is a dictionary of metadata or is left empty.
1082 """
1083 """
1083 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1084 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1084 date=None, extra=None):
1085 date=None, extra=None):
1085 self._repo = repo
1086 self._repo = repo
1086 self._rev = None
1087 self._rev = None
1087 self._node = None
1088 self._node = None
1088 self._text = text
1089 self._text = text
1089 self._date = date and util.parsedate(date) or util.makedate()
1090 self._date = date and util.parsedate(date) or util.makedate()
1090 self._user = user
1091 self._user = user
1091 parents = [(p or nullid) for p in parents]
1092 parents = [(p or nullid) for p in parents]
1092 p1, p2 = parents
1093 p1, p2 = parents
1093 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1094 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1094 files = sorted(set(files))
1095 files = sorted(set(files))
1095 self._status = [files, [], [], [], []]
1096 self._status = [files, [], [], [], []]
1096 self._filectxfn = filectxfn
1097 self._filectxfn = filectxfn
1097
1098
1098 self._extra = extra and extra.copy() or {}
1099 self._extra = extra and extra.copy() or {}
1099 if self._extra.get('branch', '') == '':
1100 if self._extra.get('branch', '') == '':
1100 self._extra['branch'] = 'default'
1101 self._extra['branch'] = 'default'
1101
1102
1102 def __str__(self):
1103 def __str__(self):
1103 return str(self._parents[0]) + "+"
1104 return str(self._parents[0]) + "+"
1104
1105
1105 def __int__(self):
1106 def __int__(self):
1106 return self._rev
1107 return self._rev
1107
1108
1108 def __nonzero__(self):
1109 def __nonzero__(self):
1109 return True
1110 return True
1110
1111
1111 def __getitem__(self, key):
1112 def __getitem__(self, key):
1112 return self.filectx(key)
1113 return self.filectx(key)
1113
1114
1114 def p1(self):
1115 def p1(self):
1115 return self._parents[0]
1116 return self._parents[0]
1116 def p2(self):
1117 def p2(self):
1117 return self._parents[1]
1118 return self._parents[1]
1118
1119
1119 def user(self):
1120 def user(self):
1120 return self._user or self._repo.ui.username()
1121 return self._user or self._repo.ui.username()
1121 def date(self):
1122 def date(self):
1122 return self._date
1123 return self._date
1123 def description(self):
1124 def description(self):
1124 return self._text
1125 return self._text
1125 def files(self):
1126 def files(self):
1126 return self.modified()
1127 return self.modified()
1127 def modified(self):
1128 def modified(self):
1128 return self._status[0]
1129 return self._status[0]
1129 def added(self):
1130 def added(self):
1130 return self._status[1]
1131 return self._status[1]
1131 def removed(self):
1132 def removed(self):
1132 return self._status[2]
1133 return self._status[2]
1133 def deleted(self):
1134 def deleted(self):
1134 return self._status[3]
1135 return self._status[3]
1135 def unknown(self):
1136 def unknown(self):
1136 return self._status[4]
1137 return self._status[4]
1137 def ignored(self):
1138 def ignored(self):
1138 return self._status[5]
1139 return self._status[5]
1139 def clean(self):
1140 def clean(self):
1140 return self._status[6]
1141 return self._status[6]
1141 def branch(self):
1142 def branch(self):
1142 return encoding.tolocal(self._extra['branch'])
1143 return encoding.tolocal(self._extra['branch'])
1143 def extra(self):
1144 def extra(self):
1144 return self._extra
1145 return self._extra
1145 def flags(self, f):
1146 def flags(self, f):
1146 return self[f].flags()
1147 return self[f].flags()
1147
1148
1148 def parents(self):
1149 def parents(self):
1149 """return contexts for each parent changeset"""
1150 """return contexts for each parent changeset"""
1150 return self._parents
1151 return self._parents
1151
1152
1152 def filectx(self, path, filelog=None):
1153 def filectx(self, path, filelog=None):
1153 """get a file context from the working directory"""
1154 """get a file context from the working directory"""
1154 return self._filectxfn(self._repo, self, path)
1155 return self._filectxfn(self._repo, self, path)
1155
1156
1156 def commit(self):
1157 def commit(self):
1157 """commit context to the repo"""
1158 """commit context to the repo"""
1158 return self._repo.commitctx(self)
1159 return self._repo.commitctx(self)
1159
1160
1160 class memfilectx(object):
1161 class memfilectx(object):
1161 """memfilectx represents an in-memory file to commit.
1162 """memfilectx represents an in-memory file to commit.
1162
1163
1163 See memctx for more details.
1164 See memctx for more details.
1164 """
1165 """
1165 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1166 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1166 """
1167 """
1167 path is the normalized file path relative to repository root.
1168 path is the normalized file path relative to repository root.
1168 data is the file content as a string.
1169 data is the file content as a string.
1169 islink is True if the file is a symbolic link.
1170 islink is True if the file is a symbolic link.
1170 isexec is True if the file is executable.
1171 isexec is True if the file is executable.
1171 copied is the source file path if current file was copied in the
1172 copied is the source file path if current file was copied in the
1172 revision being committed, or None."""
1173 revision being committed, or None."""
1173 self._path = path
1174 self._path = path
1174 self._data = data
1175 self._data = data
1175 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1176 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1176 self._copied = None
1177 self._copied = None
1177 if copied:
1178 if copied:
1178 self._copied = (copied, nullid)
1179 self._copied = (copied, nullid)
1179
1180
1180 def __nonzero__(self):
1181 def __nonzero__(self):
1181 return True
1182 return True
1182 def __str__(self):
1183 def __str__(self):
1183 return "%s@%s" % (self.path(), self._changectx)
1184 return "%s@%s" % (self.path(), self._changectx)
1184 def path(self):
1185 def path(self):
1185 return self._path
1186 return self._path
1186 def data(self):
1187 def data(self):
1187 return self._data
1188 return self._data
1188 def flags(self):
1189 def flags(self):
1189 return self._flags
1190 return self._flags
1190 def isexec(self):
1191 def isexec(self):
1191 return 'x' in self._flags
1192 return 'x' in self._flags
1192 def islink(self):
1193 def islink(self):
1193 return 'l' in self._flags
1194 return 'l' in self._flags
1194 def renamed(self):
1195 def renamed(self):
1195 return self._copied
1196 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now