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