##// END OF EJS Templates
ancestor: rewrite to deal with crossed linkrevs (issue2682)...
Matt Mackall -
r13552:7ab85fec stable
parent child Browse files
Show More
@@ -1,1112 +1,1113 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, encoding
10 import ancestor, bdiff, error, util, 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 children(self):
405 def children(self):
406 # hard for renames
406 # hard for renames
407 c = self._filelog.children(self._filenode)
407 c = self._filelog.children(self._filenode)
408 return [filectx(self._repo, self._path, fileid=x,
408 return [filectx(self._repo, self._path, fileid=x,
409 filelog=self._filelog) for x in c]
409 filelog=self._filelog) for x in c]
410
410
411 def annotate(self, follow=False, linenumber=None):
411 def annotate(self, follow=False, linenumber=None):
412 '''returns a list of tuples of (ctx, line) for each line
412 '''returns a list of tuples of (ctx, line) for each line
413 in the file, where ctx is the filectx of the node where
413 in the file, where ctx is the filectx of the node where
414 that line was last changed.
414 that line was last changed.
415 This returns tuples of ((ctx, linenumber), line) for each line,
415 This returns tuples of ((ctx, linenumber), line) for each line,
416 if "linenumber" parameter is NOT "None".
416 if "linenumber" parameter is NOT "None".
417 In such tuples, linenumber means one at the first appearance
417 In such tuples, linenumber means one at the first appearance
418 in the managed file.
418 in the managed file.
419 To reduce annotation cost,
419 To reduce annotation cost,
420 this returns fixed value(False is used) as linenumber,
420 this returns fixed value(False is used) as linenumber,
421 if "linenumber" parameter is "False".'''
421 if "linenumber" parameter is "False".'''
422
422
423 def decorate_compat(text, rev):
423 def decorate_compat(text, rev):
424 return ([rev] * len(text.splitlines()), text)
424 return ([rev] * len(text.splitlines()), text)
425
425
426 def without_linenumber(text, rev):
426 def without_linenumber(text, rev):
427 return ([(rev, False)] * len(text.splitlines()), text)
427 return ([(rev, False)] * len(text.splitlines()), text)
428
428
429 def with_linenumber(text, rev):
429 def with_linenumber(text, rev):
430 size = len(text.splitlines())
430 size = len(text.splitlines())
431 return ([(rev, i) for i in xrange(1, size + 1)], text)
431 return ([(rev, i) for i in xrange(1, size + 1)], text)
432
432
433 decorate = (((linenumber is None) and decorate_compat) or
433 decorate = (((linenumber is None) and decorate_compat) or
434 (linenumber and with_linenumber) or
434 (linenumber and with_linenumber) or
435 without_linenumber)
435 without_linenumber)
436
436
437 def pair(parent, child):
437 def pair(parent, child):
438 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
438 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
439 child[0][b1:b2] = parent[0][a1:a2]
439 child[0][b1:b2] = parent[0][a1:a2]
440 return child
440 return child
441
441
442 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
442 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
443 def getctx(path, fileid):
443 def getctx(path, fileid):
444 log = path == self._path and self._filelog or getlog(path)
444 log = path == self._path and self._filelog or getlog(path)
445 return filectx(self._repo, path, fileid=fileid, filelog=log)
445 return filectx(self._repo, path, fileid=fileid, filelog=log)
446 getctx = util.lrucachefunc(getctx)
446 getctx = util.lrucachefunc(getctx)
447
447
448 def parents(f):
448 def parents(f):
449 # we want to reuse filectx objects as much as possible
449 # we want to reuse filectx objects as much as possible
450 p = f._path
450 p = f._path
451 if f._filerev is None: # working dir
451 if f._filerev is None: # working dir
452 pl = [(n.path(), n.filerev()) for n in f.parents()]
452 pl = [(n.path(), n.filerev()) for n in f.parents()]
453 else:
453 else:
454 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
454 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
455
455
456 if follow:
456 if follow:
457 r = f.renamed()
457 r = f.renamed()
458 if r:
458 if r:
459 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
459 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
460
460
461 return [getctx(p, n) for p, n in pl if n != nullrev]
461 return [getctx(p, n) for p, n in pl if n != nullrev]
462
462
463 # use linkrev to find the first changeset where self appeared
463 # use linkrev to find the first changeset where self appeared
464 if self.rev() != self.linkrev():
464 if self.rev() != self.linkrev():
465 base = self.filectx(self.filerev())
465 base = self.filectx(self.filerev())
466 else:
466 else:
467 base = self
467 base = self
468
468
469 # find all ancestors
469 # This algorithm would prefer to be recursive, but Python is a
470 # bit recursion-hostile. Instead we do an iterative
471 # depth-first search.
472
473 visit = [base]
474 hist = {}
475 pcache = {}
470 needed = {base: 1}
476 needed = {base: 1}
471 visit = [base]
472 files = [base._path]
473 while visit:
477 while visit:
474 f = visit.pop(0)
478 f = visit[-1]
475 for p in parents(f):
479 if f not in pcache:
476 if p not in needed:
480 pcache[f] = parents(f)
477 needed[p] = 1
478 visit.append(p)
479 if p._path not in files:
480 files.append(p._path)
481 else:
482 # count how many times we'll use this
483 needed[p] += 1
484
481
485 # sort by revision (per file) which is a topological order
482 ready = True
486 visit = []
483 pl = pcache[f]
487 for f in files:
484 for p in pl:
488 visit.extend(n for n in needed if n._path == f)
485 if p not in hist:
486 ready = False
487 visit.append(p)
488 needed[p] = needed.get(p, 0) + 1
489 if ready:
490 visit.pop()
491 curr = decorate(f.data(), f)
492 for p in pl:
493 curr = pair(hist[p], curr)
494 if needed[p] == 1:
495 del hist[p]
496 else:
497 needed[p] -= 1
489
498
490 hist = {}
499 hist[f] = curr
491 for f in sorted(visit, key=lambda x: x.rev()):
500 pcache[f] = []
492 curr = decorate(f.data(), f)
493 for p in parents(f):
494 curr = pair(hist[p], curr)
495 # trim the history of unneeded revs
496 needed[p] -= 1
497 if not needed[p]:
498 del hist[p]
499 hist[f] = curr
500
501
501 return zip(hist[f][0], hist[f][1].splitlines(True))
502 return zip(hist[base][0], hist[base][1].splitlines(True))
502
503
503 def ancestor(self, fc2, actx=None):
504 def ancestor(self, fc2, actx=None):
504 """
505 """
505 find the common ancestor file context, if any, of self, and fc2
506 find the common ancestor file context, if any, of self, and fc2
506
507
507 If actx is given, it must be the changectx of the common ancestor
508 If actx is given, it must be the changectx of the common ancestor
508 of self's and fc2's respective changesets.
509 of self's and fc2's respective changesets.
509 """
510 """
510
511
511 if actx is None:
512 if actx is None:
512 actx = self.changectx().ancestor(fc2.changectx())
513 actx = self.changectx().ancestor(fc2.changectx())
513
514
514 # the trivial case: changesets are unrelated, files must be too
515 # the trivial case: changesets are unrelated, files must be too
515 if not actx:
516 if not actx:
516 return None
517 return None
517
518
518 # the easy case: no (relevant) renames
519 # the easy case: no (relevant) renames
519 if fc2.path() == self.path() and self.path() in actx:
520 if fc2.path() == self.path() and self.path() in actx:
520 return actx[self.path()]
521 return actx[self.path()]
521 acache = {}
522 acache = {}
522
523
523 # prime the ancestor cache for the working directory
524 # prime the ancestor cache for the working directory
524 for c in (self, fc2):
525 for c in (self, fc2):
525 if c._filerev is None:
526 if c._filerev is None:
526 pl = [(n.path(), n.filenode()) for n in c.parents()]
527 pl = [(n.path(), n.filenode()) for n in c.parents()]
527 acache[(c._path, None)] = pl
528 acache[(c._path, None)] = pl
528
529
529 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
530 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
530 def parents(vertex):
531 def parents(vertex):
531 if vertex in acache:
532 if vertex in acache:
532 return acache[vertex]
533 return acache[vertex]
533 f, n = vertex
534 f, n = vertex
534 if f not in flcache:
535 if f not in flcache:
535 flcache[f] = self._repo.file(f)
536 flcache[f] = self._repo.file(f)
536 fl = flcache[f]
537 fl = flcache[f]
537 pl = [(f, p) for p in fl.parents(n) if p != nullid]
538 pl = [(f, p) for p in fl.parents(n) if p != nullid]
538 re = fl.renamed(n)
539 re = fl.renamed(n)
539 if re:
540 if re:
540 pl.append(re)
541 pl.append(re)
541 acache[vertex] = pl
542 acache[vertex] = pl
542 return pl
543 return pl
543
544
544 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
545 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
545 v = ancestor.ancestor(a, b, parents)
546 v = ancestor.ancestor(a, b, parents)
546 if v:
547 if v:
547 f, n = v
548 f, n = v
548 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
549 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
549
550
550 return None
551 return None
551
552
552 def ancestors(self):
553 def ancestors(self):
553 visit = {}
554 visit = {}
554 c = self
555 c = self
555 while True:
556 while True:
556 for parent in c.parents():
557 for parent in c.parents():
557 visit[(parent.rev(), parent.node())] = parent
558 visit[(parent.rev(), parent.node())] = parent
558 if not visit:
559 if not visit:
559 break
560 break
560 c = visit.pop(max(visit))
561 c = visit.pop(max(visit))
561 yield c
562 yield c
562
563
563 class workingctx(changectx):
564 class workingctx(changectx):
564 """A workingctx object makes access to data related to
565 """A workingctx object makes access to data related to
565 the current working directory convenient.
566 the current working directory convenient.
566 date - any valid date string or (unixtime, offset), or None.
567 date - any valid date string or (unixtime, offset), or None.
567 user - username string, or None.
568 user - username string, or None.
568 extra - a dictionary of extra values, or None.
569 extra - a dictionary of extra values, or None.
569 changes - a list of file lists as returned by localrepo.status()
570 changes - a list of file lists as returned by localrepo.status()
570 or None to use the repository status.
571 or None to use the repository status.
571 """
572 """
572 def __init__(self, repo, text="", user=None, date=None, extra=None,
573 def __init__(self, repo, text="", user=None, date=None, extra=None,
573 changes=None):
574 changes=None):
574 self._repo = repo
575 self._repo = repo
575 self._rev = None
576 self._rev = None
576 self._node = None
577 self._node = None
577 self._text = text
578 self._text = text
578 if date:
579 if date:
579 self._date = util.parsedate(date)
580 self._date = util.parsedate(date)
580 if user:
581 if user:
581 self._user = user
582 self._user = user
582 if changes:
583 if changes:
583 self._status = list(changes[:4])
584 self._status = list(changes[:4])
584 self._unknown = changes[4]
585 self._unknown = changes[4]
585 self._ignored = changes[5]
586 self._ignored = changes[5]
586 self._clean = changes[6]
587 self._clean = changes[6]
587 else:
588 else:
588 self._unknown = None
589 self._unknown = None
589 self._ignored = None
590 self._ignored = None
590 self._clean = None
591 self._clean = None
591
592
592 self._extra = {}
593 self._extra = {}
593 if extra:
594 if extra:
594 self._extra = extra.copy()
595 self._extra = extra.copy()
595 if 'branch' not in self._extra:
596 if 'branch' not in self._extra:
596 try:
597 try:
597 branch = encoding.fromlocal(self._repo.dirstate.branch())
598 branch = encoding.fromlocal(self._repo.dirstate.branch())
598 except UnicodeDecodeError:
599 except UnicodeDecodeError:
599 raise util.Abort(_('branch name not in UTF-8!'))
600 raise util.Abort(_('branch name not in UTF-8!'))
600 self._extra['branch'] = branch
601 self._extra['branch'] = branch
601 if self._extra['branch'] == '':
602 if self._extra['branch'] == '':
602 self._extra['branch'] = 'default'
603 self._extra['branch'] = 'default'
603
604
604 def __str__(self):
605 def __str__(self):
605 return str(self._parents[0]) + "+"
606 return str(self._parents[0]) + "+"
606
607
607 def __repr__(self):
608 def __repr__(self):
608 return "<workingctx %s>" % str(self)
609 return "<workingctx %s>" % str(self)
609
610
610 def __nonzero__(self):
611 def __nonzero__(self):
611 return True
612 return True
612
613
613 def __contains__(self, key):
614 def __contains__(self, key):
614 return self._repo.dirstate[key] not in "?r"
615 return self._repo.dirstate[key] not in "?r"
615
616
616 @propertycache
617 @propertycache
617 def _manifest(self):
618 def _manifest(self):
618 """generate a manifest corresponding to the working directory"""
619 """generate a manifest corresponding to the working directory"""
619
620
620 if self._unknown is None:
621 if self._unknown is None:
621 self.status(unknown=True)
622 self.status(unknown=True)
622
623
623 man = self._parents[0].manifest().copy()
624 man = self._parents[0].manifest().copy()
624 copied = self._repo.dirstate.copies()
625 copied = self._repo.dirstate.copies()
625 if len(self._parents) > 1:
626 if len(self._parents) > 1:
626 man2 = self.p2().manifest()
627 man2 = self.p2().manifest()
627 def getman(f):
628 def getman(f):
628 if f in man:
629 if f in man:
629 return man
630 return man
630 return man2
631 return man2
631 else:
632 else:
632 getman = lambda f: man
633 getman = lambda f: man
633 def cf(f):
634 def cf(f):
634 f = copied.get(f, f)
635 f = copied.get(f, f)
635 return getman(f).flags(f)
636 return getman(f).flags(f)
636 ff = self._repo.dirstate.flagfunc(cf)
637 ff = self._repo.dirstate.flagfunc(cf)
637 modified, added, removed, deleted = self._status
638 modified, added, removed, deleted = self._status
638 unknown = self._unknown
639 unknown = self._unknown
639 for i, l in (("a", added), ("m", modified), ("u", unknown)):
640 for i, l in (("a", added), ("m", modified), ("u", unknown)):
640 for f in l:
641 for f in l:
641 orig = copied.get(f, f)
642 orig = copied.get(f, f)
642 man[f] = getman(orig).get(orig, nullid) + i
643 man[f] = getman(orig).get(orig, nullid) + i
643 try:
644 try:
644 man.set(f, ff(f))
645 man.set(f, ff(f))
645 except OSError:
646 except OSError:
646 pass
647 pass
647
648
648 for f in deleted + removed:
649 for f in deleted + removed:
649 if f in man:
650 if f in man:
650 del man[f]
651 del man[f]
651
652
652 return man
653 return man
653
654
654 @propertycache
655 @propertycache
655 def _status(self):
656 def _status(self):
656 return self._repo.status()[:4]
657 return self._repo.status()[:4]
657
658
658 @propertycache
659 @propertycache
659 def _user(self):
660 def _user(self):
660 return self._repo.ui.username()
661 return self._repo.ui.username()
661
662
662 @propertycache
663 @propertycache
663 def _date(self):
664 def _date(self):
664 return util.makedate()
665 return util.makedate()
665
666
666 @propertycache
667 @propertycache
667 def _parents(self):
668 def _parents(self):
668 p = self._repo.dirstate.parents()
669 p = self._repo.dirstate.parents()
669 if p[1] == nullid:
670 if p[1] == nullid:
670 p = p[:-1]
671 p = p[:-1]
671 self._parents = [changectx(self._repo, x) for x in p]
672 self._parents = [changectx(self._repo, x) for x in p]
672 return self._parents
673 return self._parents
673
674
674 def status(self, ignored=False, clean=False, unknown=False):
675 def status(self, ignored=False, clean=False, unknown=False):
675 """Explicit status query
676 """Explicit status query
676 Unless this method is used to query the working copy status, the
677 Unless this method is used to query the working copy status, the
677 _status property will implicitly read the status using its default
678 _status property will implicitly read the status using its default
678 arguments."""
679 arguments."""
679 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
680 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
680 self._unknown = self._ignored = self._clean = None
681 self._unknown = self._ignored = self._clean = None
681 if unknown:
682 if unknown:
682 self._unknown = stat[4]
683 self._unknown = stat[4]
683 if ignored:
684 if ignored:
684 self._ignored = stat[5]
685 self._ignored = stat[5]
685 if clean:
686 if clean:
686 self._clean = stat[6]
687 self._clean = stat[6]
687 self._status = stat[:4]
688 self._status = stat[:4]
688 return stat
689 return stat
689
690
690 def manifest(self):
691 def manifest(self):
691 return self._manifest
692 return self._manifest
692 def user(self):
693 def user(self):
693 return self._user or self._repo.ui.username()
694 return self._user or self._repo.ui.username()
694 def date(self):
695 def date(self):
695 return self._date
696 return self._date
696 def description(self):
697 def description(self):
697 return self._text
698 return self._text
698 def files(self):
699 def files(self):
699 return sorted(self._status[0] + self._status[1] + self._status[2])
700 return sorted(self._status[0] + self._status[1] + self._status[2])
700
701
701 def modified(self):
702 def modified(self):
702 return self._status[0]
703 return self._status[0]
703 def added(self):
704 def added(self):
704 return self._status[1]
705 return self._status[1]
705 def removed(self):
706 def removed(self):
706 return self._status[2]
707 return self._status[2]
707 def deleted(self):
708 def deleted(self):
708 return self._status[3]
709 return self._status[3]
709 def unknown(self):
710 def unknown(self):
710 assert self._unknown is not None # must call status first
711 assert self._unknown is not None # must call status first
711 return self._unknown
712 return self._unknown
712 def ignored(self):
713 def ignored(self):
713 assert self._ignored is not None # must call status first
714 assert self._ignored is not None # must call status first
714 return self._ignored
715 return self._ignored
715 def clean(self):
716 def clean(self):
716 assert self._clean is not None # must call status first
717 assert self._clean is not None # must call status first
717 return self._clean
718 return self._clean
718 def branch(self):
719 def branch(self):
719 return encoding.tolocal(self._extra['branch'])
720 return encoding.tolocal(self._extra['branch'])
720 def extra(self):
721 def extra(self):
721 return self._extra
722 return self._extra
722
723
723 def tags(self):
724 def tags(self):
724 t = []
725 t = []
725 for p in self.parents():
726 for p in self.parents():
726 t.extend(p.tags())
727 t.extend(p.tags())
727 return t
728 return t
728
729
729 def bookmarks(self):
730 def bookmarks(self):
730 b = []
731 b = []
731 for p in self.parents():
732 for p in self.parents():
732 b.extend(p.bookmarks())
733 b.extend(p.bookmarks())
733 return b
734 return b
734
735
735 def children(self):
736 def children(self):
736 return []
737 return []
737
738
738 def flags(self, path):
739 def flags(self, path):
739 if '_manifest' in self.__dict__:
740 if '_manifest' in self.__dict__:
740 try:
741 try:
741 return self._manifest.flags(path)
742 return self._manifest.flags(path)
742 except KeyError:
743 except KeyError:
743 return ''
744 return ''
744
745
745 orig = self._repo.dirstate.copies().get(path, path)
746 orig = self._repo.dirstate.copies().get(path, path)
746
747
747 def findflag(ctx):
748 def findflag(ctx):
748 mnode = ctx.changeset()[0]
749 mnode = ctx.changeset()[0]
749 node, flag = self._repo.manifest.find(mnode, orig)
750 node, flag = self._repo.manifest.find(mnode, orig)
750 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
751 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
751 try:
752 try:
752 return ff(path)
753 return ff(path)
753 except OSError:
754 except OSError:
754 pass
755 pass
755
756
756 flag = findflag(self._parents[0])
757 flag = findflag(self._parents[0])
757 if flag is None and len(self.parents()) > 1:
758 if flag is None and len(self.parents()) > 1:
758 flag = findflag(self._parents[1])
759 flag = findflag(self._parents[1])
759 if flag is None or self._repo.dirstate[path] == 'r':
760 if flag is None or self._repo.dirstate[path] == 'r':
760 return ''
761 return ''
761 return flag
762 return flag
762
763
763 def filectx(self, path, filelog=None):
764 def filectx(self, path, filelog=None):
764 """get a file context from the working directory"""
765 """get a file context from the working directory"""
765 return workingfilectx(self._repo, path, workingctx=self,
766 return workingfilectx(self._repo, path, workingctx=self,
766 filelog=filelog)
767 filelog=filelog)
767
768
768 def ancestor(self, c2):
769 def ancestor(self, c2):
769 """return the ancestor context of self and c2"""
770 """return the ancestor context of self and c2"""
770 return self._parents[0].ancestor(c2) # punt on two parents for now
771 return self._parents[0].ancestor(c2) # punt on two parents for now
771
772
772 def walk(self, match):
773 def walk(self, match):
773 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
774 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
774 True, False))
775 True, False))
775
776
776 def dirty(self, missing=False):
777 def dirty(self, missing=False):
777 "check whether a working directory is modified"
778 "check whether a working directory is modified"
778 # check subrepos first
779 # check subrepos first
779 for s in self.substate:
780 for s in self.substate:
780 if self.sub(s).dirty():
781 if self.sub(s).dirty():
781 return True
782 return True
782 # check current working dir
783 # check current working dir
783 return (self.p2() or self.branch() != self.p1().branch() or
784 return (self.p2() or self.branch() != self.p1().branch() or
784 self.modified() or self.added() or self.removed() or
785 self.modified() or self.added() or self.removed() or
785 (missing and self.deleted()))
786 (missing and self.deleted()))
786
787
787 def add(self, list, prefix=""):
788 def add(self, list, prefix=""):
788 join = lambda f: os.path.join(prefix, f)
789 join = lambda f: os.path.join(prefix, f)
789 wlock = self._repo.wlock()
790 wlock = self._repo.wlock()
790 ui, ds = self._repo.ui, self._repo.dirstate
791 ui, ds = self._repo.ui, self._repo.dirstate
791 try:
792 try:
792 rejected = []
793 rejected = []
793 for f in list:
794 for f in list:
794 p = self._repo.wjoin(f)
795 p = self._repo.wjoin(f)
795 try:
796 try:
796 st = os.lstat(p)
797 st = os.lstat(p)
797 except:
798 except:
798 ui.warn(_("%s does not exist!\n") % join(f))
799 ui.warn(_("%s does not exist!\n") % join(f))
799 rejected.append(f)
800 rejected.append(f)
800 continue
801 continue
801 if st.st_size > 10000000:
802 if st.st_size > 10000000:
802 ui.warn(_("%s: up to %d MB of RAM may be required "
803 ui.warn(_("%s: up to %d MB of RAM may be required "
803 "to manage this file\n"
804 "to manage this file\n"
804 "(use 'hg revert %s' to cancel the "
805 "(use 'hg revert %s' to cancel the "
805 "pending addition)\n")
806 "pending addition)\n")
806 % (f, 3 * st.st_size // 1000000, join(f)))
807 % (f, 3 * st.st_size // 1000000, join(f)))
807 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
808 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
808 ui.warn(_("%s not added: only files and symlinks "
809 ui.warn(_("%s not added: only files and symlinks "
809 "supported currently\n") % join(f))
810 "supported currently\n") % join(f))
810 rejected.append(p)
811 rejected.append(p)
811 elif ds[f] in 'amn':
812 elif ds[f] in 'amn':
812 ui.warn(_("%s already tracked!\n") % join(f))
813 ui.warn(_("%s already tracked!\n") % join(f))
813 elif ds[f] == 'r':
814 elif ds[f] == 'r':
814 ds.normallookup(f)
815 ds.normallookup(f)
815 else:
816 else:
816 ds.add(f)
817 ds.add(f)
817 return rejected
818 return rejected
818 finally:
819 finally:
819 wlock.release()
820 wlock.release()
820
821
821 def forget(self, list):
822 def forget(self, list):
822 wlock = self._repo.wlock()
823 wlock = self._repo.wlock()
823 try:
824 try:
824 for f in list:
825 for f in list:
825 if self._repo.dirstate[f] != 'a':
826 if self._repo.dirstate[f] != 'a':
826 self._repo.ui.warn(_("%s not added!\n") % f)
827 self._repo.ui.warn(_("%s not added!\n") % f)
827 else:
828 else:
828 self._repo.dirstate.forget(f)
829 self._repo.dirstate.forget(f)
829 finally:
830 finally:
830 wlock.release()
831 wlock.release()
831
832
832 def ancestors(self):
833 def ancestors(self):
833 for a in self._repo.changelog.ancestors(
834 for a in self._repo.changelog.ancestors(
834 *[p.rev() for p in self._parents]):
835 *[p.rev() for p in self._parents]):
835 yield changectx(self._repo, a)
836 yield changectx(self._repo, a)
836
837
837 def remove(self, list, unlink=False):
838 def remove(self, list, unlink=False):
838 if unlink:
839 if unlink:
839 for f in list:
840 for f in list:
840 try:
841 try:
841 util.unlinkpath(self._repo.wjoin(f))
842 util.unlinkpath(self._repo.wjoin(f))
842 except OSError, inst:
843 except OSError, inst:
843 if inst.errno != errno.ENOENT:
844 if inst.errno != errno.ENOENT:
844 raise
845 raise
845 wlock = self._repo.wlock()
846 wlock = self._repo.wlock()
846 try:
847 try:
847 for f in list:
848 for f in list:
848 if unlink and os.path.lexists(self._repo.wjoin(f)):
849 if unlink and os.path.lexists(self._repo.wjoin(f)):
849 self._repo.ui.warn(_("%s still exists!\n") % f)
850 self._repo.ui.warn(_("%s still exists!\n") % f)
850 elif self._repo.dirstate[f] == 'a':
851 elif self._repo.dirstate[f] == 'a':
851 self._repo.dirstate.forget(f)
852 self._repo.dirstate.forget(f)
852 elif f not in self._repo.dirstate:
853 elif f not in self._repo.dirstate:
853 self._repo.ui.warn(_("%s not tracked!\n") % f)
854 self._repo.ui.warn(_("%s not tracked!\n") % f)
854 else:
855 else:
855 self._repo.dirstate.remove(f)
856 self._repo.dirstate.remove(f)
856 finally:
857 finally:
857 wlock.release()
858 wlock.release()
858
859
859 def undelete(self, list):
860 def undelete(self, list):
860 pctxs = self.parents()
861 pctxs = self.parents()
861 wlock = self._repo.wlock()
862 wlock = self._repo.wlock()
862 try:
863 try:
863 for f in list:
864 for f in list:
864 if self._repo.dirstate[f] != 'r':
865 if self._repo.dirstate[f] != 'r':
865 self._repo.ui.warn(_("%s not removed!\n") % f)
866 self._repo.ui.warn(_("%s not removed!\n") % f)
866 else:
867 else:
867 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
868 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
868 t = fctx.data()
869 t = fctx.data()
869 self._repo.wwrite(f, t, fctx.flags())
870 self._repo.wwrite(f, t, fctx.flags())
870 self._repo.dirstate.normal(f)
871 self._repo.dirstate.normal(f)
871 finally:
872 finally:
872 wlock.release()
873 wlock.release()
873
874
874 def copy(self, source, dest):
875 def copy(self, source, dest):
875 p = self._repo.wjoin(dest)
876 p = self._repo.wjoin(dest)
876 if not os.path.lexists(p):
877 if not os.path.lexists(p):
877 self._repo.ui.warn(_("%s does not exist!\n") % dest)
878 self._repo.ui.warn(_("%s does not exist!\n") % dest)
878 elif not (os.path.isfile(p) or os.path.islink(p)):
879 elif not (os.path.isfile(p) or os.path.islink(p)):
879 self._repo.ui.warn(_("copy failed: %s is not a file or a "
880 self._repo.ui.warn(_("copy failed: %s is not a file or a "
880 "symbolic link\n") % dest)
881 "symbolic link\n") % dest)
881 else:
882 else:
882 wlock = self._repo.wlock()
883 wlock = self._repo.wlock()
883 try:
884 try:
884 if self._repo.dirstate[dest] in '?r':
885 if self._repo.dirstate[dest] in '?r':
885 self._repo.dirstate.add(dest)
886 self._repo.dirstate.add(dest)
886 self._repo.dirstate.copy(source, dest)
887 self._repo.dirstate.copy(source, dest)
887 finally:
888 finally:
888 wlock.release()
889 wlock.release()
889
890
890 class workingfilectx(filectx):
891 class workingfilectx(filectx):
891 """A workingfilectx object makes access to data related to a particular
892 """A workingfilectx object makes access to data related to a particular
892 file in the working directory convenient."""
893 file in the working directory convenient."""
893 def __init__(self, repo, path, filelog=None, workingctx=None):
894 def __init__(self, repo, path, filelog=None, workingctx=None):
894 """changeid can be a changeset revision, node, or tag.
895 """changeid can be a changeset revision, node, or tag.
895 fileid can be a file revision or node."""
896 fileid can be a file revision or node."""
896 self._repo = repo
897 self._repo = repo
897 self._path = path
898 self._path = path
898 self._changeid = None
899 self._changeid = None
899 self._filerev = self._filenode = None
900 self._filerev = self._filenode = None
900
901
901 if filelog:
902 if filelog:
902 self._filelog = filelog
903 self._filelog = filelog
903 if workingctx:
904 if workingctx:
904 self._changectx = workingctx
905 self._changectx = workingctx
905
906
906 @propertycache
907 @propertycache
907 def _changectx(self):
908 def _changectx(self):
908 return workingctx(self._repo)
909 return workingctx(self._repo)
909
910
910 def __nonzero__(self):
911 def __nonzero__(self):
911 return True
912 return True
912
913
913 def __str__(self):
914 def __str__(self):
914 return "%s@%s" % (self.path(), self._changectx)
915 return "%s@%s" % (self.path(), self._changectx)
915
916
916 def __repr__(self):
917 def __repr__(self):
917 return "<workingfilectx %s>" % str(self)
918 return "<workingfilectx %s>" % str(self)
918
919
919 def data(self):
920 def data(self):
920 return self._repo.wread(self._path)
921 return self._repo.wread(self._path)
921 def renamed(self):
922 def renamed(self):
922 rp = self._repo.dirstate.copied(self._path)
923 rp = self._repo.dirstate.copied(self._path)
923 if not rp:
924 if not rp:
924 return None
925 return None
925 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
926 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
926
927
927 def parents(self):
928 def parents(self):
928 '''return parent filectxs, following copies if necessary'''
929 '''return parent filectxs, following copies if necessary'''
929 def filenode(ctx, path):
930 def filenode(ctx, path):
930 return ctx._manifest.get(path, nullid)
931 return ctx._manifest.get(path, nullid)
931
932
932 path = self._path
933 path = self._path
933 fl = self._filelog
934 fl = self._filelog
934 pcl = self._changectx._parents
935 pcl = self._changectx._parents
935 renamed = self.renamed()
936 renamed = self.renamed()
936
937
937 if renamed:
938 if renamed:
938 pl = [renamed + (None,)]
939 pl = [renamed + (None,)]
939 else:
940 else:
940 pl = [(path, filenode(pcl[0], path), fl)]
941 pl = [(path, filenode(pcl[0], path), fl)]
941
942
942 for pc in pcl[1:]:
943 for pc in pcl[1:]:
943 pl.append((path, filenode(pc, path), fl))
944 pl.append((path, filenode(pc, path), fl))
944
945
945 return [filectx(self._repo, p, fileid=n, filelog=l)
946 return [filectx(self._repo, p, fileid=n, filelog=l)
946 for p, n, l in pl if n != nullid]
947 for p, n, l in pl if n != nullid]
947
948
948 def children(self):
949 def children(self):
949 return []
950 return []
950
951
951 def size(self):
952 def size(self):
952 return os.lstat(self._repo.wjoin(self._path)).st_size
953 return os.lstat(self._repo.wjoin(self._path)).st_size
953 def date(self):
954 def date(self):
954 t, tz = self._changectx.date()
955 t, tz = self._changectx.date()
955 try:
956 try:
956 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
957 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
957 except OSError, err:
958 except OSError, err:
958 if err.errno != errno.ENOENT:
959 if err.errno != errno.ENOENT:
959 raise
960 raise
960 return (t, tz)
961 return (t, tz)
961
962
962 def cmp(self, fctx):
963 def cmp(self, fctx):
963 """compare with other file context
964 """compare with other file context
964
965
965 returns True if different than fctx.
966 returns True if different than fctx.
966 """
967 """
967 # fctx should be a filectx (not a wfctx)
968 # fctx should be a filectx (not a wfctx)
968 # invert comparison to reuse the same code path
969 # invert comparison to reuse the same code path
969 return fctx.cmp(self)
970 return fctx.cmp(self)
970
971
971 class memctx(object):
972 class memctx(object):
972 """Use memctx to perform in-memory commits via localrepo.commitctx().
973 """Use memctx to perform in-memory commits via localrepo.commitctx().
973
974
974 Revision information is supplied at initialization time while
975 Revision information is supplied at initialization time while
975 related files data and is made available through a callback
976 related files data and is made available through a callback
976 mechanism. 'repo' is the current localrepo, 'parents' is a
977 mechanism. 'repo' is the current localrepo, 'parents' is a
977 sequence of two parent revisions identifiers (pass None for every
978 sequence of two parent revisions identifiers (pass None for every
978 missing parent), 'text' is the commit message and 'files' lists
979 missing parent), 'text' is the commit message and 'files' lists
979 names of files touched by the revision (normalized and relative to
980 names of files touched by the revision (normalized and relative to
980 repository root).
981 repository root).
981
982
982 filectxfn(repo, memctx, path) is a callable receiving the
983 filectxfn(repo, memctx, path) is a callable receiving the
983 repository, the current memctx object and the normalized path of
984 repository, the current memctx object and the normalized path of
984 requested file, relative to repository root. It is fired by the
985 requested file, relative to repository root. It is fired by the
985 commit function for every file in 'files', but calls order is
986 commit function for every file in 'files', but calls order is
986 undefined. If the file is available in the revision being
987 undefined. If the file is available in the revision being
987 committed (updated or added), filectxfn returns a memfilectx
988 committed (updated or added), filectxfn returns a memfilectx
988 object. If the file was removed, filectxfn raises an
989 object. If the file was removed, filectxfn raises an
989 IOError. Moved files are represented by marking the source file
990 IOError. Moved files are represented by marking the source file
990 removed and the new file added with copy information (see
991 removed and the new file added with copy information (see
991 memfilectx).
992 memfilectx).
992
993
993 user receives the committer name and defaults to current
994 user receives the committer name and defaults to current
994 repository username, date is the commit date in any format
995 repository username, date is the commit date in any format
995 supported by util.parsedate() and defaults to current date, extra
996 supported by util.parsedate() and defaults to current date, extra
996 is a dictionary of metadata or is left empty.
997 is a dictionary of metadata or is left empty.
997 """
998 """
998 def __init__(self, repo, parents, text, files, filectxfn, user=None,
999 def __init__(self, repo, parents, text, files, filectxfn, user=None,
999 date=None, extra=None):
1000 date=None, extra=None):
1000 self._repo = repo
1001 self._repo = repo
1001 self._rev = None
1002 self._rev = None
1002 self._node = None
1003 self._node = None
1003 self._text = text
1004 self._text = text
1004 self._date = date and util.parsedate(date) or util.makedate()
1005 self._date = date and util.parsedate(date) or util.makedate()
1005 self._user = user
1006 self._user = user
1006 parents = [(p or nullid) for p in parents]
1007 parents = [(p or nullid) for p in parents]
1007 p1, p2 = parents
1008 p1, p2 = parents
1008 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1009 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1009 files = sorted(set(files))
1010 files = sorted(set(files))
1010 self._status = [files, [], [], [], []]
1011 self._status = [files, [], [], [], []]
1011 self._filectxfn = filectxfn
1012 self._filectxfn = filectxfn
1012
1013
1013 self._extra = extra and extra.copy() or {}
1014 self._extra = extra and extra.copy() or {}
1014 if 'branch' not in self._extra:
1015 if 'branch' not in self._extra:
1015 self._extra['branch'] = 'default'
1016 self._extra['branch'] = 'default'
1016 elif self._extra.get('branch') == '':
1017 elif self._extra.get('branch') == '':
1017 self._extra['branch'] = 'default'
1018 self._extra['branch'] = 'default'
1018
1019
1019 def __str__(self):
1020 def __str__(self):
1020 return str(self._parents[0]) + "+"
1021 return str(self._parents[0]) + "+"
1021
1022
1022 def __int__(self):
1023 def __int__(self):
1023 return self._rev
1024 return self._rev
1024
1025
1025 def __nonzero__(self):
1026 def __nonzero__(self):
1026 return True
1027 return True
1027
1028
1028 def __getitem__(self, key):
1029 def __getitem__(self, key):
1029 return self.filectx(key)
1030 return self.filectx(key)
1030
1031
1031 def p1(self):
1032 def p1(self):
1032 return self._parents[0]
1033 return self._parents[0]
1033 def p2(self):
1034 def p2(self):
1034 return self._parents[1]
1035 return self._parents[1]
1035
1036
1036 def user(self):
1037 def user(self):
1037 return self._user or self._repo.ui.username()
1038 return self._user or self._repo.ui.username()
1038 def date(self):
1039 def date(self):
1039 return self._date
1040 return self._date
1040 def description(self):
1041 def description(self):
1041 return self._text
1042 return self._text
1042 def files(self):
1043 def files(self):
1043 return self.modified()
1044 return self.modified()
1044 def modified(self):
1045 def modified(self):
1045 return self._status[0]
1046 return self._status[0]
1046 def added(self):
1047 def added(self):
1047 return self._status[1]
1048 return self._status[1]
1048 def removed(self):
1049 def removed(self):
1049 return self._status[2]
1050 return self._status[2]
1050 def deleted(self):
1051 def deleted(self):
1051 return self._status[3]
1052 return self._status[3]
1052 def unknown(self):
1053 def unknown(self):
1053 return self._status[4]
1054 return self._status[4]
1054 def ignored(self):
1055 def ignored(self):
1055 return self._status[5]
1056 return self._status[5]
1056 def clean(self):
1057 def clean(self):
1057 return self._status[6]
1058 return self._status[6]
1058 def branch(self):
1059 def branch(self):
1059 return encoding.tolocal(self._extra['branch'])
1060 return encoding.tolocal(self._extra['branch'])
1060 def extra(self):
1061 def extra(self):
1061 return self._extra
1062 return self._extra
1062 def flags(self, f):
1063 def flags(self, f):
1063 return self[f].flags()
1064 return self[f].flags()
1064
1065
1065 def parents(self):
1066 def parents(self):
1066 """return contexts for each parent changeset"""
1067 """return contexts for each parent changeset"""
1067 return self._parents
1068 return self._parents
1068
1069
1069 def filectx(self, path, filelog=None):
1070 def filectx(self, path, filelog=None):
1070 """get a file context from the working directory"""
1071 """get a file context from the working directory"""
1071 return self._filectxfn(self._repo, self, path)
1072 return self._filectxfn(self._repo, self, path)
1072
1073
1073 def commit(self):
1074 def commit(self):
1074 """commit context to the repo"""
1075 """commit context to the repo"""
1075 return self._repo.commitctx(self)
1076 return self._repo.commitctx(self)
1076
1077
1077 class memfilectx(object):
1078 class memfilectx(object):
1078 """memfilectx represents an in-memory file to commit.
1079 """memfilectx represents an in-memory file to commit.
1079
1080
1080 See memctx for more details.
1081 See memctx for more details.
1081 """
1082 """
1082 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1083 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1083 """
1084 """
1084 path is the normalized file path relative to repository root.
1085 path is the normalized file path relative to repository root.
1085 data is the file content as a string.
1086 data is the file content as a string.
1086 islink is True if the file is a symbolic link.
1087 islink is True if the file is a symbolic link.
1087 isexec is True if the file is executable.
1088 isexec is True if the file is executable.
1088 copied is the source file path if current file was copied in the
1089 copied is the source file path if current file was copied in the
1089 revision being committed, or None."""
1090 revision being committed, or None."""
1090 self._path = path
1091 self._path = path
1091 self._data = data
1092 self._data = data
1092 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1093 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1093 self._copied = None
1094 self._copied = None
1094 if copied:
1095 if copied:
1095 self._copied = (copied, nullid)
1096 self._copied = (copied, nullid)
1096
1097
1097 def __nonzero__(self):
1098 def __nonzero__(self):
1098 return True
1099 return True
1099 def __str__(self):
1100 def __str__(self):
1100 return "%s@%s" % (self.path(), self._changectx)
1101 return "%s@%s" % (self.path(), self._changectx)
1101 def path(self):
1102 def path(self):
1102 return self._path
1103 return self._path
1103 def data(self):
1104 def data(self):
1104 return self._data
1105 return self._data
1105 def flags(self):
1106 def flags(self):
1106 return self._flags
1107 return self._flags
1107 def isexec(self):
1108 def isexec(self):
1108 return 'x' in self._flags
1109 return 'x' in self._flags
1109 def islink(self):
1110 def islink(self):
1110 return 'l' in self._flags
1111 return 'l' in self._flags
1111 def renamed(self):
1112 def renamed(self):
1112 return self._copied
1113 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now