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