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