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