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