##// END OF EJS Templates
commitablectx: move _user from workingctx
Sean Farley -
r19674:ec5b2e2b default
parent child Browse files
Show More
@@ -1,1403 +1,1403 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, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import copies
11 import copies
12 import match as matchmod
12 import match as matchmod
13 import os, errno, stat
13 import os, errno, stat
14 import obsolete as obsmod
14 import obsolete as obsmod
15 import repoview
15 import repoview
16
16
17 propertycache = util.propertycache
17 propertycache = util.propertycache
18
18
19 class basectx(object):
19 class basectx(object):
20 """A basectx object represents the common logic for its children:
20 """A basectx object represents the common logic for its children:
21 changectx: read-only context that is already present in the repo,
21 changectx: read-only context that is already present in the repo,
22 workingctx: a context that represents the working directory and can
22 workingctx: a context that represents the working directory and can
23 be committed,
23 be committed,
24 memctx: a context that represents changes in-memory and can also
24 memctx: a context that represents changes in-memory and can also
25 be committed."""
25 be committed."""
26 def __new__(cls, repo, changeid='', *args, **kwargs):
26 def __new__(cls, repo, changeid='', *args, **kwargs):
27 if isinstance(changeid, basectx):
27 if isinstance(changeid, basectx):
28 return changeid
28 return changeid
29
29
30 o = super(basectx, cls).__new__(cls)
30 o = super(basectx, cls).__new__(cls)
31
31
32 o._repo = repo
32 o._repo = repo
33 o._rev = nullrev
33 o._rev = nullrev
34 o._node = nullid
34 o._node = nullid
35
35
36 return o
36 return o
37
37
38 def __str__(self):
38 def __str__(self):
39 return short(self.node())
39 return short(self.node())
40
40
41 def __int__(self):
41 def __int__(self):
42 return self.rev()
42 return self.rev()
43
43
44 def __repr__(self):
44 def __repr__(self):
45 return "<%s %s>" % (type(self).__name__, str(self))
45 return "<%s %s>" % (type(self).__name__, str(self))
46
46
47 def __eq__(self, other):
47 def __eq__(self, other):
48 try:
48 try:
49 return type(self) == type(other) and self._rev == other._rev
49 return type(self) == type(other) and self._rev == other._rev
50 except AttributeError:
50 except AttributeError:
51 return False
51 return False
52
52
53 def __ne__(self, other):
53 def __ne__(self, other):
54 return not (self == other)
54 return not (self == other)
55
55
56 def __contains__(self, key):
56 def __contains__(self, key):
57 return key in self._manifest
57 return key in self._manifest
58
58
59 def __getitem__(self, key):
59 def __getitem__(self, key):
60 return self.filectx(key)
60 return self.filectx(key)
61
61
62 def __iter__(self):
62 def __iter__(self):
63 for f in sorted(self._manifest):
63 for f in sorted(self._manifest):
64 yield f
64 yield f
65
65
66 @propertycache
66 @propertycache
67 def substate(self):
67 def substate(self):
68 return subrepo.state(self, self._repo.ui)
68 return subrepo.state(self, self._repo.ui)
69
69
70 def rev(self):
70 def rev(self):
71 return self._rev
71 return self._rev
72 def node(self):
72 def node(self):
73 return self._node
73 return self._node
74 def hex(self):
74 def hex(self):
75 return hex(self.node())
75 return hex(self.node())
76 def manifest(self):
76 def manifest(self):
77 return self._manifest
77 return self._manifest
78 def phasestr(self):
78 def phasestr(self):
79 return phases.phasenames[self.phase()]
79 return phases.phasenames[self.phase()]
80 def mutable(self):
80 def mutable(self):
81 return self.phase() > phases.public
81 return self.phase() > phases.public
82
82
83 def parents(self):
83 def parents(self):
84 """return contexts for each parent changeset"""
84 """return contexts for each parent changeset"""
85 return self._parents
85 return self._parents
86
86
87 def p1(self):
87 def p1(self):
88 return self._parents[0]
88 return self._parents[0]
89
89
90 def p2(self):
90 def p2(self):
91 if len(self._parents) == 2:
91 if len(self._parents) == 2:
92 return self._parents[1]
92 return self._parents[1]
93 return changectx(self._repo, -1)
93 return changectx(self._repo, -1)
94
94
95 def _fileinfo(self, path):
95 def _fileinfo(self, path):
96 if '_manifest' in self.__dict__:
96 if '_manifest' in self.__dict__:
97 try:
97 try:
98 return self._manifest[path], self._manifest.flags(path)
98 return self._manifest[path], self._manifest.flags(path)
99 except KeyError:
99 except KeyError:
100 raise error.ManifestLookupError(self._node, path,
100 raise error.ManifestLookupError(self._node, path,
101 _('not found in manifest'))
101 _('not found in manifest'))
102 if '_manifestdelta' in self.__dict__ or path in self.files():
102 if '_manifestdelta' in self.__dict__ or path in self.files():
103 if path in self._manifestdelta:
103 if path in self._manifestdelta:
104 return (self._manifestdelta[path],
104 return (self._manifestdelta[path],
105 self._manifestdelta.flags(path))
105 self._manifestdelta.flags(path))
106 node, flag = self._repo.manifest.find(self._changeset[0], path)
106 node, flag = self._repo.manifest.find(self._changeset[0], path)
107 if not node:
107 if not node:
108 raise error.ManifestLookupError(self._node, path,
108 raise error.ManifestLookupError(self._node, path,
109 _('not found in manifest'))
109 _('not found in manifest'))
110
110
111 return node, flag
111 return node, flag
112
112
113 def filenode(self, path):
113 def filenode(self, path):
114 return self._fileinfo(path)[0]
114 return self._fileinfo(path)[0]
115
115
116 def flags(self, path):
116 def flags(self, path):
117 try:
117 try:
118 return self._fileinfo(path)[1]
118 return self._fileinfo(path)[1]
119 except error.LookupError:
119 except error.LookupError:
120 return ''
120 return ''
121
121
122 def sub(self, path):
122 def sub(self, path):
123 return subrepo.subrepo(self, path)
123 return subrepo.subrepo(self, path)
124
124
125 def match(self, pats=[], include=None, exclude=None, default='glob'):
125 def match(self, pats=[], include=None, exclude=None, default='glob'):
126 r = self._repo
126 r = self._repo
127 return matchmod.match(r.root, r.getcwd(), pats,
127 return matchmod.match(r.root, r.getcwd(), pats,
128 include, exclude, default,
128 include, exclude, default,
129 auditor=r.auditor, ctx=self)
129 auditor=r.auditor, ctx=self)
130
130
131 def diff(self, ctx2=None, match=None, **opts):
131 def diff(self, ctx2=None, match=None, **opts):
132 """Returns a diff generator for the given contexts and matcher"""
132 """Returns a diff generator for the given contexts and matcher"""
133 if ctx2 is None:
133 if ctx2 is None:
134 ctx2 = self.p1()
134 ctx2 = self.p1()
135 if ctx2 is not None:
135 if ctx2 is not None:
136 ctx2 = self._repo[ctx2]
136 ctx2 = self._repo[ctx2]
137 diffopts = patch.diffopts(self._repo.ui, opts)
137 diffopts = patch.diffopts(self._repo.ui, opts)
138 return patch.diff(self._repo, ctx2.node(), self.node(),
138 return patch.diff(self._repo, ctx2.node(), self.node(),
139 match=match, opts=diffopts)
139 match=match, opts=diffopts)
140
140
141 @propertycache
141 @propertycache
142 def _dirs(self):
142 def _dirs(self):
143 return scmutil.dirs(self._manifest)
143 return scmutil.dirs(self._manifest)
144
144
145 def dirs(self):
145 def dirs(self):
146 return self._dirs
146 return self._dirs
147
147
148 def dirty(self):
148 def dirty(self):
149 return False
149 return False
150
150
151 class changectx(basectx):
151 class changectx(basectx):
152 """A changecontext object makes access to data related to a particular
152 """A changecontext object makes access to data related to a particular
153 changeset convenient. It represents a read-only context already presnt in
153 changeset convenient. It represents a read-only context already presnt in
154 the repo."""
154 the repo."""
155 def __init__(self, repo, changeid=''):
155 def __init__(self, repo, changeid=''):
156 """changeid is a revision number, node, or tag"""
156 """changeid is a revision number, node, or tag"""
157
157
158 # since basectx.__new__ already took care of copying the object, we
158 # since basectx.__new__ already took care of copying the object, we
159 # don't need to do anything in __init__, so we just exit here
159 # don't need to do anything in __init__, so we just exit here
160 if isinstance(changeid, basectx):
160 if isinstance(changeid, basectx):
161 return
161 return
162
162
163 if changeid == '':
163 if changeid == '':
164 changeid = '.'
164 changeid = '.'
165 self._repo = repo
165 self._repo = repo
166
166
167 if isinstance(changeid, int):
167 if isinstance(changeid, int):
168 try:
168 try:
169 self._node = repo.changelog.node(changeid)
169 self._node = repo.changelog.node(changeid)
170 except IndexError:
170 except IndexError:
171 raise error.RepoLookupError(
171 raise error.RepoLookupError(
172 _("unknown revision '%s'") % changeid)
172 _("unknown revision '%s'") % changeid)
173 self._rev = changeid
173 self._rev = changeid
174 return
174 return
175 if isinstance(changeid, long):
175 if isinstance(changeid, long):
176 changeid = str(changeid)
176 changeid = str(changeid)
177 if changeid == '.':
177 if changeid == '.':
178 self._node = repo.dirstate.p1()
178 self._node = repo.dirstate.p1()
179 self._rev = repo.changelog.rev(self._node)
179 self._rev = repo.changelog.rev(self._node)
180 return
180 return
181 if changeid == 'null':
181 if changeid == 'null':
182 self._node = nullid
182 self._node = nullid
183 self._rev = nullrev
183 self._rev = nullrev
184 return
184 return
185 if changeid == 'tip':
185 if changeid == 'tip':
186 self._node = repo.changelog.tip()
186 self._node = repo.changelog.tip()
187 self._rev = repo.changelog.rev(self._node)
187 self._rev = repo.changelog.rev(self._node)
188 return
188 return
189 if len(changeid) == 20:
189 if len(changeid) == 20:
190 try:
190 try:
191 self._node = changeid
191 self._node = changeid
192 self._rev = repo.changelog.rev(changeid)
192 self._rev = repo.changelog.rev(changeid)
193 return
193 return
194 except LookupError:
194 except LookupError:
195 pass
195 pass
196
196
197 try:
197 try:
198 r = int(changeid)
198 r = int(changeid)
199 if str(r) != changeid:
199 if str(r) != changeid:
200 raise ValueError
200 raise ValueError
201 l = len(repo.changelog)
201 l = len(repo.changelog)
202 if r < 0:
202 if r < 0:
203 r += l
203 r += l
204 if r < 0 or r >= l:
204 if r < 0 or r >= l:
205 raise ValueError
205 raise ValueError
206 self._rev = r
206 self._rev = r
207 self._node = repo.changelog.node(r)
207 self._node = repo.changelog.node(r)
208 return
208 return
209 except (ValueError, OverflowError, IndexError):
209 except (ValueError, OverflowError, IndexError):
210 pass
210 pass
211
211
212 if len(changeid) == 40:
212 if len(changeid) == 40:
213 try:
213 try:
214 self._node = bin(changeid)
214 self._node = bin(changeid)
215 self._rev = repo.changelog.rev(self._node)
215 self._rev = repo.changelog.rev(self._node)
216 return
216 return
217 except (TypeError, LookupError):
217 except (TypeError, LookupError):
218 pass
218 pass
219
219
220 if changeid in repo._bookmarks:
220 if changeid in repo._bookmarks:
221 self._node = repo._bookmarks[changeid]
221 self._node = repo._bookmarks[changeid]
222 self._rev = repo.changelog.rev(self._node)
222 self._rev = repo.changelog.rev(self._node)
223 return
223 return
224 if changeid in repo._tagscache.tags:
224 if changeid in repo._tagscache.tags:
225 self._node = repo._tagscache.tags[changeid]
225 self._node = repo._tagscache.tags[changeid]
226 self._rev = repo.changelog.rev(self._node)
226 self._rev = repo.changelog.rev(self._node)
227 return
227 return
228 try:
228 try:
229 self._node = repo.branchtip(changeid)
229 self._node = repo.branchtip(changeid)
230 self._rev = repo.changelog.rev(self._node)
230 self._rev = repo.changelog.rev(self._node)
231 return
231 return
232 except error.RepoLookupError:
232 except error.RepoLookupError:
233 pass
233 pass
234
234
235 self._node = repo.changelog._partialmatch(changeid)
235 self._node = repo.changelog._partialmatch(changeid)
236 if self._node is not None:
236 if self._node is not None:
237 self._rev = repo.changelog.rev(self._node)
237 self._rev = repo.changelog.rev(self._node)
238 return
238 return
239
239
240 # lookup failed
240 # lookup failed
241 # check if it might have come from damaged dirstate
241 # check if it might have come from damaged dirstate
242 #
242 #
243 # XXX we could avoid the unfiltered if we had a recognizable exception
243 # XXX we could avoid the unfiltered if we had a recognizable exception
244 # for filtered changeset access
244 # for filtered changeset access
245 if changeid in repo.unfiltered().dirstate.parents():
245 if changeid in repo.unfiltered().dirstate.parents():
246 raise error.Abort(_("working directory has unknown parent '%s'!")
246 raise error.Abort(_("working directory has unknown parent '%s'!")
247 % short(changeid))
247 % short(changeid))
248 try:
248 try:
249 if len(changeid) == 20:
249 if len(changeid) == 20:
250 changeid = hex(changeid)
250 changeid = hex(changeid)
251 except TypeError:
251 except TypeError:
252 pass
252 pass
253 raise error.RepoLookupError(
253 raise error.RepoLookupError(
254 _("unknown revision '%s'") % changeid)
254 _("unknown revision '%s'") % changeid)
255
255
256 def __hash__(self):
256 def __hash__(self):
257 try:
257 try:
258 return hash(self._rev)
258 return hash(self._rev)
259 except AttributeError:
259 except AttributeError:
260 return id(self)
260 return id(self)
261
261
262 def __nonzero__(self):
262 def __nonzero__(self):
263 return self._rev != nullrev
263 return self._rev != nullrev
264
264
265 @propertycache
265 @propertycache
266 def _changeset(self):
266 def _changeset(self):
267 return self._repo.changelog.read(self.rev())
267 return self._repo.changelog.read(self.rev())
268
268
269 @propertycache
269 @propertycache
270 def _manifest(self):
270 def _manifest(self):
271 return self._repo.manifest.read(self._changeset[0])
271 return self._repo.manifest.read(self._changeset[0])
272
272
273 @propertycache
273 @propertycache
274 def _manifestdelta(self):
274 def _manifestdelta(self):
275 return self._repo.manifest.readdelta(self._changeset[0])
275 return self._repo.manifest.readdelta(self._changeset[0])
276
276
277 @propertycache
277 @propertycache
278 def _parents(self):
278 def _parents(self):
279 p = self._repo.changelog.parentrevs(self._rev)
279 p = self._repo.changelog.parentrevs(self._rev)
280 if p[1] == nullrev:
280 if p[1] == nullrev:
281 p = p[:-1]
281 p = p[:-1]
282 return [changectx(self._repo, x) for x in p]
282 return [changectx(self._repo, x) for x in p]
283
283
284 def changeset(self):
284 def changeset(self):
285 return self._changeset
285 return self._changeset
286 def manifestnode(self):
286 def manifestnode(self):
287 return self._changeset[0]
287 return self._changeset[0]
288
288
289 def user(self):
289 def user(self):
290 return self._changeset[1]
290 return self._changeset[1]
291 def date(self):
291 def date(self):
292 return self._changeset[2]
292 return self._changeset[2]
293 def files(self):
293 def files(self):
294 return self._changeset[3]
294 return self._changeset[3]
295 def description(self):
295 def description(self):
296 return self._changeset[4]
296 return self._changeset[4]
297 def branch(self):
297 def branch(self):
298 return encoding.tolocal(self._changeset[5].get("branch"))
298 return encoding.tolocal(self._changeset[5].get("branch"))
299 def closesbranch(self):
299 def closesbranch(self):
300 return 'close' in self._changeset[5]
300 return 'close' in self._changeset[5]
301 def extra(self):
301 def extra(self):
302 return self._changeset[5]
302 return self._changeset[5]
303 def tags(self):
303 def tags(self):
304 return self._repo.nodetags(self._node)
304 return self._repo.nodetags(self._node)
305 def bookmarks(self):
305 def bookmarks(self):
306 return self._repo.nodebookmarks(self._node)
306 return self._repo.nodebookmarks(self._node)
307 def phase(self):
307 def phase(self):
308 return self._repo._phasecache.phase(self._repo, self._rev)
308 return self._repo._phasecache.phase(self._repo, self._rev)
309 def hidden(self):
309 def hidden(self):
310 return self._rev in repoview.filterrevs(self._repo, 'visible')
310 return self._rev in repoview.filterrevs(self._repo, 'visible')
311
311
312 def children(self):
312 def children(self):
313 """return contexts for each child changeset"""
313 """return contexts for each child changeset"""
314 c = self._repo.changelog.children(self._node)
314 c = self._repo.changelog.children(self._node)
315 return [changectx(self._repo, x) for x in c]
315 return [changectx(self._repo, x) for x in c]
316
316
317 def ancestors(self):
317 def ancestors(self):
318 for a in self._repo.changelog.ancestors([self._rev]):
318 for a in self._repo.changelog.ancestors([self._rev]):
319 yield changectx(self._repo, a)
319 yield changectx(self._repo, a)
320
320
321 def descendants(self):
321 def descendants(self):
322 for d in self._repo.changelog.descendants([self._rev]):
322 for d in self._repo.changelog.descendants([self._rev]):
323 yield changectx(self._repo, d)
323 yield changectx(self._repo, d)
324
324
325 def obsolete(self):
325 def obsolete(self):
326 """True if the changeset is obsolete"""
326 """True if the changeset is obsolete"""
327 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
327 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
328
328
329 def extinct(self):
329 def extinct(self):
330 """True if the changeset is extinct"""
330 """True if the changeset is extinct"""
331 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
331 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
332
332
333 def unstable(self):
333 def unstable(self):
334 """True if the changeset is not obsolete but it's ancestor are"""
334 """True if the changeset is not obsolete but it's ancestor are"""
335 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
335 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
336
336
337 def bumped(self):
337 def bumped(self):
338 """True if the changeset try to be a successor of a public changeset
338 """True if the changeset try to be a successor of a public changeset
339
339
340 Only non-public and non-obsolete changesets may be bumped.
340 Only non-public and non-obsolete changesets may be bumped.
341 """
341 """
342 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
342 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
343
343
344 def divergent(self):
344 def divergent(self):
345 """Is a successors of a changeset with multiple possible successors set
345 """Is a successors of a changeset with multiple possible successors set
346
346
347 Only non-public and non-obsolete changesets may be divergent.
347 Only non-public and non-obsolete changesets may be divergent.
348 """
348 """
349 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
349 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
350
350
351 def troubled(self):
351 def troubled(self):
352 """True if the changeset is either unstable, bumped or divergent"""
352 """True if the changeset is either unstable, bumped or divergent"""
353 return self.unstable() or self.bumped() or self.divergent()
353 return self.unstable() or self.bumped() or self.divergent()
354
354
355 def troubles(self):
355 def troubles(self):
356 """return the list of troubles affecting this changesets.
356 """return the list of troubles affecting this changesets.
357
357
358 Troubles are returned as strings. possible values are:
358 Troubles are returned as strings. possible values are:
359 - unstable,
359 - unstable,
360 - bumped,
360 - bumped,
361 - divergent.
361 - divergent.
362 """
362 """
363 troubles = []
363 troubles = []
364 if self.unstable():
364 if self.unstable():
365 troubles.append('unstable')
365 troubles.append('unstable')
366 if self.bumped():
366 if self.bumped():
367 troubles.append('bumped')
367 troubles.append('bumped')
368 if self.divergent():
368 if self.divergent():
369 troubles.append('divergent')
369 troubles.append('divergent')
370 return troubles
370 return troubles
371
371
372 def filectx(self, path, fileid=None, filelog=None):
372 def filectx(self, path, fileid=None, filelog=None):
373 """get a file context from this changeset"""
373 """get a file context from this changeset"""
374 if fileid is None:
374 if fileid is None:
375 fileid = self.filenode(path)
375 fileid = self.filenode(path)
376 return filectx(self._repo, path, fileid=fileid,
376 return filectx(self._repo, path, fileid=fileid,
377 changectx=self, filelog=filelog)
377 changectx=self, filelog=filelog)
378
378
379 def ancestor(self, c2):
379 def ancestor(self, c2):
380 """
380 """
381 return the ancestor context of self and c2
381 return the ancestor context of self and c2
382 """
382 """
383 # deal with workingctxs
383 # deal with workingctxs
384 n2 = c2._node
384 n2 = c2._node
385 if n2 is None:
385 if n2 is None:
386 n2 = c2._parents[0]._node
386 n2 = c2._parents[0]._node
387 n = self._repo.changelog.ancestor(self._node, n2)
387 n = self._repo.changelog.ancestor(self._node, n2)
388 return changectx(self._repo, n)
388 return changectx(self._repo, n)
389
389
390 def descendant(self, other):
390 def descendant(self, other):
391 """True if other is descendant of this changeset"""
391 """True if other is descendant of this changeset"""
392 return self._repo.changelog.descendant(self._rev, other._rev)
392 return self._repo.changelog.descendant(self._rev, other._rev)
393
393
394 def walk(self, match):
394 def walk(self, match):
395 fset = set(match.files())
395 fset = set(match.files())
396 # for dirstate.walk, files=['.'] means "walk the whole tree".
396 # for dirstate.walk, files=['.'] means "walk the whole tree".
397 # follow that here, too
397 # follow that here, too
398 fset.discard('.')
398 fset.discard('.')
399 for fn in self:
399 for fn in self:
400 if fn in fset:
400 if fn in fset:
401 # specified pattern is the exact name
401 # specified pattern is the exact name
402 fset.remove(fn)
402 fset.remove(fn)
403 if match(fn):
403 if match(fn):
404 yield fn
404 yield fn
405 for fn in sorted(fset):
405 for fn in sorted(fset):
406 if fn in self._dirs:
406 if fn in self._dirs:
407 # specified pattern is a directory
407 # specified pattern is a directory
408 continue
408 continue
409 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
409 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
410 yield fn
410 yield fn
411
411
412 class basefilectx(object):
412 class basefilectx(object):
413 """A filecontext object represents the common logic for its children:
413 """A filecontext object represents the common logic for its children:
414 filectx: read-only access to a filerevision that is already present
414 filectx: read-only access to a filerevision that is already present
415 in the repo,
415 in the repo,
416 workingfilectx: a filecontext that represents files from the working
416 workingfilectx: a filecontext that represents files from the working
417 directory,
417 directory,
418 memfilectx: a filecontext that represents files in-memory."""
418 memfilectx: a filecontext that represents files in-memory."""
419 def __new__(cls, repo, path, *args, **kwargs):
419 def __new__(cls, repo, path, *args, **kwargs):
420 return super(basefilectx, cls).__new__(cls)
420 return super(basefilectx, cls).__new__(cls)
421
421
422 @propertycache
422 @propertycache
423 def _filelog(self):
423 def _filelog(self):
424 return self._repo.file(self._path)
424 return self._repo.file(self._path)
425
425
426 @propertycache
426 @propertycache
427 def _changeid(self):
427 def _changeid(self):
428 if '_changeid' in self.__dict__:
428 if '_changeid' in self.__dict__:
429 return self._changeid
429 return self._changeid
430 elif '_changectx' in self.__dict__:
430 elif '_changectx' in self.__dict__:
431 return self._changectx.rev()
431 return self._changectx.rev()
432 else:
432 else:
433 return self._filelog.linkrev(self._filerev)
433 return self._filelog.linkrev(self._filerev)
434
434
435 @propertycache
435 @propertycache
436 def _filenode(self):
436 def _filenode(self):
437 if '_fileid' in self.__dict__:
437 if '_fileid' in self.__dict__:
438 return self._filelog.lookup(self._fileid)
438 return self._filelog.lookup(self._fileid)
439 else:
439 else:
440 return self._changectx.filenode(self._path)
440 return self._changectx.filenode(self._path)
441
441
442 @propertycache
442 @propertycache
443 def _filerev(self):
443 def _filerev(self):
444 return self._filelog.rev(self._filenode)
444 return self._filelog.rev(self._filenode)
445
445
446 @propertycache
446 @propertycache
447 def _repopath(self):
447 def _repopath(self):
448 return self._path
448 return self._path
449
449
450 def __nonzero__(self):
450 def __nonzero__(self):
451 try:
451 try:
452 self._filenode
452 self._filenode
453 return True
453 return True
454 except error.LookupError:
454 except error.LookupError:
455 # file is missing
455 # file is missing
456 return False
456 return False
457
457
458 def __str__(self):
458 def __str__(self):
459 return "%s@%s" % (self.path(), self._changectx)
459 return "%s@%s" % (self.path(), self._changectx)
460
460
461 def __repr__(self):
461 def __repr__(self):
462 return "<%s %s>" % (type(self).__name__, str(self))
462 return "<%s %s>" % (type(self).__name__, str(self))
463
463
464 def __hash__(self):
464 def __hash__(self):
465 try:
465 try:
466 return hash((self._path, self._filenode))
466 return hash((self._path, self._filenode))
467 except AttributeError:
467 except AttributeError:
468 return id(self)
468 return id(self)
469
469
470 def __eq__(self, other):
470 def __eq__(self, other):
471 try:
471 try:
472 return (type(self) == type(other) and self._path == other._path
472 return (type(self) == type(other) and self._path == other._path
473 and self._filenode == other._filenode)
473 and self._filenode == other._filenode)
474 except AttributeError:
474 except AttributeError:
475 return False
475 return False
476
476
477 def __ne__(self, other):
477 def __ne__(self, other):
478 return not (self == other)
478 return not (self == other)
479
479
480 def filerev(self):
480 def filerev(self):
481 return self._filerev
481 return self._filerev
482 def filenode(self):
482 def filenode(self):
483 return self._filenode
483 return self._filenode
484 def flags(self):
484 def flags(self):
485 return self._changectx.flags(self._path)
485 return self._changectx.flags(self._path)
486 def filelog(self):
486 def filelog(self):
487 return self._filelog
487 return self._filelog
488 def rev(self):
488 def rev(self):
489 return self._changeid
489 return self._changeid
490 def linkrev(self):
490 def linkrev(self):
491 return self._filelog.linkrev(self._filerev)
491 return self._filelog.linkrev(self._filerev)
492 def node(self):
492 def node(self):
493 return self._changectx.node()
493 return self._changectx.node()
494 def hex(self):
494 def hex(self):
495 return self._changectx.hex()
495 return self._changectx.hex()
496 def user(self):
496 def user(self):
497 return self._changectx.user()
497 return self._changectx.user()
498 def date(self):
498 def date(self):
499 return self._changectx.date()
499 return self._changectx.date()
500 def files(self):
500 def files(self):
501 return self._changectx.files()
501 return self._changectx.files()
502 def description(self):
502 def description(self):
503 return self._changectx.description()
503 return self._changectx.description()
504 def branch(self):
504 def branch(self):
505 return self._changectx.branch()
505 return self._changectx.branch()
506 def extra(self):
506 def extra(self):
507 return self._changectx.extra()
507 return self._changectx.extra()
508 def phase(self):
508 def phase(self):
509 return self._changectx.phase()
509 return self._changectx.phase()
510 def phasestr(self):
510 def phasestr(self):
511 return self._changectx.phasestr()
511 return self._changectx.phasestr()
512 def manifest(self):
512 def manifest(self):
513 return self._changectx.manifest()
513 return self._changectx.manifest()
514 def changectx(self):
514 def changectx(self):
515 return self._changectx
515 return self._changectx
516
516
517 def path(self):
517 def path(self):
518 return self._path
518 return self._path
519
519
520 def isbinary(self):
520 def isbinary(self):
521 try:
521 try:
522 return util.binary(self.data())
522 return util.binary(self.data())
523 except IOError:
523 except IOError:
524 return False
524 return False
525
525
526 def cmp(self, fctx):
526 def cmp(self, fctx):
527 """compare with other file context
527 """compare with other file context
528
528
529 returns True if different than fctx.
529 returns True if different than fctx.
530 """
530 """
531 if (fctx._filerev is None
531 if (fctx._filerev is None
532 and (self._repo._encodefilterpats
532 and (self._repo._encodefilterpats
533 # if file data starts with '\1\n', empty metadata block is
533 # if file data starts with '\1\n', empty metadata block is
534 # prepended, which adds 4 bytes to filelog.size().
534 # prepended, which adds 4 bytes to filelog.size().
535 or self.size() - 4 == fctx.size())
535 or self.size() - 4 == fctx.size())
536 or self.size() == fctx.size()):
536 or self.size() == fctx.size()):
537 return self._filelog.cmp(self._filenode, fctx.data())
537 return self._filelog.cmp(self._filenode, fctx.data())
538
538
539 return True
539 return True
540
540
541 def parents(self):
541 def parents(self):
542 p = self._path
542 p = self._path
543 fl = self._filelog
543 fl = self._filelog
544 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
544 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
545
545
546 r = self._filelog.renamed(self._filenode)
546 r = self._filelog.renamed(self._filenode)
547 if r:
547 if r:
548 pl[0] = (r[0], r[1], None)
548 pl[0] = (r[0], r[1], None)
549
549
550 return [filectx(self._repo, p, fileid=n, filelog=l)
550 return [filectx(self._repo, p, fileid=n, filelog=l)
551 for p, n, l in pl if n != nullid]
551 for p, n, l in pl if n != nullid]
552
552
553 def p1(self):
553 def p1(self):
554 return self.parents()[0]
554 return self.parents()[0]
555
555
556 def p2(self):
556 def p2(self):
557 p = self.parents()
557 p = self.parents()
558 if len(p) == 2:
558 if len(p) == 2:
559 return p[1]
559 return p[1]
560 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
560 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
561
561
562 def annotate(self, follow=False, linenumber=None, diffopts=None):
562 def annotate(self, follow=False, linenumber=None, diffopts=None):
563 '''returns a list of tuples of (ctx, line) for each line
563 '''returns a list of tuples of (ctx, line) for each line
564 in the file, where ctx is the filectx of the node where
564 in the file, where ctx is the filectx of the node where
565 that line was last changed.
565 that line was last changed.
566 This returns tuples of ((ctx, linenumber), line) for each line,
566 This returns tuples of ((ctx, linenumber), line) for each line,
567 if "linenumber" parameter is NOT "None".
567 if "linenumber" parameter is NOT "None".
568 In such tuples, linenumber means one at the first appearance
568 In such tuples, linenumber means one at the first appearance
569 in the managed file.
569 in the managed file.
570 To reduce annotation cost,
570 To reduce annotation cost,
571 this returns fixed value(False is used) as linenumber,
571 this returns fixed value(False is used) as linenumber,
572 if "linenumber" parameter is "False".'''
572 if "linenumber" parameter is "False".'''
573
573
574 def decorate_compat(text, rev):
574 def decorate_compat(text, rev):
575 return ([rev] * len(text.splitlines()), text)
575 return ([rev] * len(text.splitlines()), text)
576
576
577 def without_linenumber(text, rev):
577 def without_linenumber(text, rev):
578 return ([(rev, False)] * len(text.splitlines()), text)
578 return ([(rev, False)] * len(text.splitlines()), text)
579
579
580 def with_linenumber(text, rev):
580 def with_linenumber(text, rev):
581 size = len(text.splitlines())
581 size = len(text.splitlines())
582 return ([(rev, i) for i in xrange(1, size + 1)], text)
582 return ([(rev, i) for i in xrange(1, size + 1)], text)
583
583
584 decorate = (((linenumber is None) and decorate_compat) or
584 decorate = (((linenumber is None) and decorate_compat) or
585 (linenumber and with_linenumber) or
585 (linenumber and with_linenumber) or
586 without_linenumber)
586 without_linenumber)
587
587
588 def pair(parent, child):
588 def pair(parent, child):
589 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
589 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
590 refine=True)
590 refine=True)
591 for (a1, a2, b1, b2), t in blocks:
591 for (a1, a2, b1, b2), t in blocks:
592 # Changed blocks ('!') or blocks made only of blank lines ('~')
592 # Changed blocks ('!') or blocks made only of blank lines ('~')
593 # belong to the child.
593 # belong to the child.
594 if t == '=':
594 if t == '=':
595 child[0][b1:b2] = parent[0][a1:a2]
595 child[0][b1:b2] = parent[0][a1:a2]
596 return child
596 return child
597
597
598 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
598 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
599
599
600 def parents(f):
600 def parents(f):
601 pl = f.parents()
601 pl = f.parents()
602
602
603 # Don't return renamed parents if we aren't following.
603 # Don't return renamed parents if we aren't following.
604 if not follow:
604 if not follow:
605 pl = [p for p in pl if p.path() == f.path()]
605 pl = [p for p in pl if p.path() == f.path()]
606
606
607 # renamed filectx won't have a filelog yet, so set it
607 # renamed filectx won't have a filelog yet, so set it
608 # from the cache to save time
608 # from the cache to save time
609 for p in pl:
609 for p in pl:
610 if not '_filelog' in p.__dict__:
610 if not '_filelog' in p.__dict__:
611 p._filelog = getlog(p.path())
611 p._filelog = getlog(p.path())
612
612
613 return pl
613 return pl
614
614
615 # use linkrev to find the first changeset where self appeared
615 # use linkrev to find the first changeset where self appeared
616 if self.rev() != self.linkrev():
616 if self.rev() != self.linkrev():
617 base = self.filectx(self.filenode())
617 base = self.filectx(self.filenode())
618 else:
618 else:
619 base = self
619 base = self
620
620
621 # This algorithm would prefer to be recursive, but Python is a
621 # This algorithm would prefer to be recursive, but Python is a
622 # bit recursion-hostile. Instead we do an iterative
622 # bit recursion-hostile. Instead we do an iterative
623 # depth-first search.
623 # depth-first search.
624
624
625 visit = [base]
625 visit = [base]
626 hist = {}
626 hist = {}
627 pcache = {}
627 pcache = {}
628 needed = {base: 1}
628 needed = {base: 1}
629 while visit:
629 while visit:
630 f = visit[-1]
630 f = visit[-1]
631 pcached = f in pcache
631 pcached = f in pcache
632 if not pcached:
632 if not pcached:
633 pcache[f] = parents(f)
633 pcache[f] = parents(f)
634
634
635 ready = True
635 ready = True
636 pl = pcache[f]
636 pl = pcache[f]
637 for p in pl:
637 for p in pl:
638 if p not in hist:
638 if p not in hist:
639 ready = False
639 ready = False
640 visit.append(p)
640 visit.append(p)
641 if not pcached:
641 if not pcached:
642 needed[p] = needed.get(p, 0) + 1
642 needed[p] = needed.get(p, 0) + 1
643 if ready:
643 if ready:
644 visit.pop()
644 visit.pop()
645 reusable = f in hist
645 reusable = f in hist
646 if reusable:
646 if reusable:
647 curr = hist[f]
647 curr = hist[f]
648 else:
648 else:
649 curr = decorate(f.data(), f)
649 curr = decorate(f.data(), f)
650 for p in pl:
650 for p in pl:
651 if not reusable:
651 if not reusable:
652 curr = pair(hist[p], curr)
652 curr = pair(hist[p], curr)
653 if needed[p] == 1:
653 if needed[p] == 1:
654 del hist[p]
654 del hist[p]
655 del needed[p]
655 del needed[p]
656 else:
656 else:
657 needed[p] -= 1
657 needed[p] -= 1
658
658
659 hist[f] = curr
659 hist[f] = curr
660 pcache[f] = []
660 pcache[f] = []
661
661
662 return zip(hist[base][0], hist[base][1].splitlines(True))
662 return zip(hist[base][0], hist[base][1].splitlines(True))
663
663
664 def ancestor(self, fc2, actx):
664 def ancestor(self, fc2, actx):
665 """
665 """
666 find the common ancestor file context, if any, of self, and fc2
666 find the common ancestor file context, if any, of self, and fc2
667
667
668 actx must be the changectx of the common ancestor
668 actx must be the changectx of the common ancestor
669 of self's and fc2's respective changesets.
669 of self's and fc2's respective changesets.
670 """
670 """
671
671
672 # the easy case: no (relevant) renames
672 # the easy case: no (relevant) renames
673 if fc2.path() == self.path() and self.path() in actx:
673 if fc2.path() == self.path() and self.path() in actx:
674 return actx[self.path()]
674 return actx[self.path()]
675
675
676 # the next easiest cases: unambiguous predecessor (name trumps
676 # the next easiest cases: unambiguous predecessor (name trumps
677 # history)
677 # history)
678 if self.path() in actx and fc2.path() not in actx:
678 if self.path() in actx and fc2.path() not in actx:
679 return actx[self.path()]
679 return actx[self.path()]
680 if fc2.path() in actx and self.path() not in actx:
680 if fc2.path() in actx and self.path() not in actx:
681 return actx[fc2.path()]
681 return actx[fc2.path()]
682
682
683 # prime the ancestor cache for the working directory
683 # prime the ancestor cache for the working directory
684 acache = {}
684 acache = {}
685 for c in (self, fc2):
685 for c in (self, fc2):
686 if c.filenode() is None:
686 if c.filenode() is None:
687 pl = [(n.path(), n.filenode()) for n in c.parents()]
687 pl = [(n.path(), n.filenode()) for n in c.parents()]
688 acache[(c._path, None)] = pl
688 acache[(c._path, None)] = pl
689
689
690 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
690 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
691 def parents(vertex):
691 def parents(vertex):
692 if vertex in acache:
692 if vertex in acache:
693 return acache[vertex]
693 return acache[vertex]
694 f, n = vertex
694 f, n = vertex
695 if f not in flcache:
695 if f not in flcache:
696 flcache[f] = self._repo.file(f)
696 flcache[f] = self._repo.file(f)
697 fl = flcache[f]
697 fl = flcache[f]
698 pl = [(f, p) for p in fl.parents(n) if p != nullid]
698 pl = [(f, p) for p in fl.parents(n) if p != nullid]
699 re = fl.renamed(n)
699 re = fl.renamed(n)
700 if re:
700 if re:
701 pl.append(re)
701 pl.append(re)
702 acache[vertex] = pl
702 acache[vertex] = pl
703 return pl
703 return pl
704
704
705 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
705 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
706 v = ancestor.genericancestor(a, b, parents)
706 v = ancestor.genericancestor(a, b, parents)
707 if v:
707 if v:
708 f, n = v
708 f, n = v
709 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
709 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
710
710
711 return None
711 return None
712
712
713 def ancestors(self, followfirst=False):
713 def ancestors(self, followfirst=False):
714 visit = {}
714 visit = {}
715 c = self
715 c = self
716 cut = followfirst and 1 or None
716 cut = followfirst and 1 or None
717 while True:
717 while True:
718 for parent in c.parents()[:cut]:
718 for parent in c.parents()[:cut]:
719 visit[(parent.rev(), parent.node())] = parent
719 visit[(parent.rev(), parent.node())] = parent
720 if not visit:
720 if not visit:
721 break
721 break
722 c = visit.pop(max(visit))
722 c = visit.pop(max(visit))
723 yield c
723 yield c
724
724
725 def copies(self, c2):
725 def copies(self, c2):
726 if not util.safehasattr(self, "_copycache"):
726 if not util.safehasattr(self, "_copycache"):
727 self._copycache = {}
727 self._copycache = {}
728 sc2 = str(c2)
728 sc2 = str(c2)
729 if sc2 not in self._copycache:
729 if sc2 not in self._copycache:
730 self._copycache[sc2] = copies.pathcopies(c2)
730 self._copycache[sc2] = copies.pathcopies(c2)
731 return self._copycache[sc2]
731 return self._copycache[sc2]
732
732
733 class filectx(basefilectx):
733 class filectx(basefilectx):
734 """A filecontext object makes access to data related to a particular
734 """A filecontext object makes access to data related to a particular
735 filerevision convenient."""
735 filerevision convenient."""
736 def __init__(self, repo, path, changeid=None, fileid=None,
736 def __init__(self, repo, path, changeid=None, fileid=None,
737 filelog=None, changectx=None):
737 filelog=None, changectx=None):
738 """changeid can be a changeset revision, node, or tag.
738 """changeid can be a changeset revision, node, or tag.
739 fileid can be a file revision or node."""
739 fileid can be a file revision or node."""
740 self._repo = repo
740 self._repo = repo
741 self._path = path
741 self._path = path
742
742
743 assert (changeid is not None
743 assert (changeid is not None
744 or fileid is not None
744 or fileid is not None
745 or changectx is not None), \
745 or changectx is not None), \
746 ("bad args: changeid=%r, fileid=%r, changectx=%r"
746 ("bad args: changeid=%r, fileid=%r, changectx=%r"
747 % (changeid, fileid, changectx))
747 % (changeid, fileid, changectx))
748
748
749 if filelog is not None:
749 if filelog is not None:
750 self._filelog = filelog
750 self._filelog = filelog
751
751
752 if changeid is not None:
752 if changeid is not None:
753 self._changeid = changeid
753 self._changeid = changeid
754 if changectx is not None:
754 if changectx is not None:
755 self._changectx = changectx
755 self._changectx = changectx
756 if fileid is not None:
756 if fileid is not None:
757 self._fileid = fileid
757 self._fileid = fileid
758
758
759 @propertycache
759 @propertycache
760 def _changectx(self):
760 def _changectx(self):
761 try:
761 try:
762 return changectx(self._repo, self._changeid)
762 return changectx(self._repo, self._changeid)
763 except error.RepoLookupError:
763 except error.RepoLookupError:
764 # Linkrev may point to any revision in the repository. When the
764 # Linkrev may point to any revision in the repository. When the
765 # repository is filtered this may lead to `filectx` trying to build
765 # repository is filtered this may lead to `filectx` trying to build
766 # `changectx` for filtered revision. In such case we fallback to
766 # `changectx` for filtered revision. In such case we fallback to
767 # creating `changectx` on the unfiltered version of the reposition.
767 # creating `changectx` on the unfiltered version of the reposition.
768 # This fallback should not be an issue because `changectx` from
768 # This fallback should not be an issue because `changectx` from
769 # `filectx` are not used in complex operations that care about
769 # `filectx` are not used in complex operations that care about
770 # filtering.
770 # filtering.
771 #
771 #
772 # This fallback is a cheap and dirty fix that prevent several
772 # This fallback is a cheap and dirty fix that prevent several
773 # crashes. It does not ensure the behavior is correct. However the
773 # crashes. It does not ensure the behavior is correct. However the
774 # behavior was not correct before filtering either and "incorrect
774 # behavior was not correct before filtering either and "incorrect
775 # behavior" is seen as better as "crash"
775 # behavior" is seen as better as "crash"
776 #
776 #
777 # Linkrevs have several serious troubles with filtering that are
777 # Linkrevs have several serious troubles with filtering that are
778 # complicated to solve. Proper handling of the issue here should be
778 # complicated to solve. Proper handling of the issue here should be
779 # considered when solving linkrev issue are on the table.
779 # considered when solving linkrev issue are on the table.
780 return changectx(self._repo.unfiltered(), self._changeid)
780 return changectx(self._repo.unfiltered(), self._changeid)
781
781
782 def filectx(self, fileid):
782 def filectx(self, fileid):
783 '''opens an arbitrary revision of the file without
783 '''opens an arbitrary revision of the file without
784 opening a new filelog'''
784 opening a new filelog'''
785 return filectx(self._repo, self._path, fileid=fileid,
785 return filectx(self._repo, self._path, fileid=fileid,
786 filelog=self._filelog)
786 filelog=self._filelog)
787
787
788 def data(self):
788 def data(self):
789 return self._filelog.read(self._filenode)
789 return self._filelog.read(self._filenode)
790 def size(self):
790 def size(self):
791 return self._filelog.size(self._filerev)
791 return self._filelog.size(self._filerev)
792
792
793 def renamed(self):
793 def renamed(self):
794 """check if file was actually renamed in this changeset revision
794 """check if file was actually renamed in this changeset revision
795
795
796 If rename logged in file revision, we report copy for changeset only
796 If rename logged in file revision, we report copy for changeset only
797 if file revisions linkrev points back to the changeset in question
797 if file revisions linkrev points back to the changeset in question
798 or both changeset parents contain different file revisions.
798 or both changeset parents contain different file revisions.
799 """
799 """
800
800
801 renamed = self._filelog.renamed(self._filenode)
801 renamed = self._filelog.renamed(self._filenode)
802 if not renamed:
802 if not renamed:
803 return renamed
803 return renamed
804
804
805 if self.rev() == self.linkrev():
805 if self.rev() == self.linkrev():
806 return renamed
806 return renamed
807
807
808 name = self.path()
808 name = self.path()
809 fnode = self._filenode
809 fnode = self._filenode
810 for p in self._changectx.parents():
810 for p in self._changectx.parents():
811 try:
811 try:
812 if fnode == p.filenode(name):
812 if fnode == p.filenode(name):
813 return None
813 return None
814 except error.LookupError:
814 except error.LookupError:
815 pass
815 pass
816 return renamed
816 return renamed
817
817
818 def children(self):
818 def children(self):
819 # hard for renames
819 # hard for renames
820 c = self._filelog.children(self._filenode)
820 c = self._filelog.children(self._filenode)
821 return [filectx(self._repo, self._path, fileid=x,
821 return [filectx(self._repo, self._path, fileid=x,
822 filelog=self._filelog) for x in c]
822 filelog=self._filelog) for x in c]
823
823
824 class commitablectx(basectx):
824 class commitablectx(basectx):
825 """A commitablectx object provides common functionality for a context that
825 """A commitablectx object provides common functionality for a context that
826 wants the ability to commit, e.g. workingctx or memctx."""
826 wants the ability to commit, e.g. workingctx or memctx."""
827 def __init__(self, repo, text="", user=None, date=None, extra=None,
827 def __init__(self, repo, text="", user=None, date=None, extra=None,
828 changes=None):
828 changes=None):
829 self._repo = repo
829 self._repo = repo
830 self._rev = None
830 self._rev = None
831 self._node = None
831 self._node = None
832 self._text = text
832 self._text = text
833 if date:
833 if date:
834 self._date = util.parsedate(date)
834 self._date = util.parsedate(date)
835 if user:
835 if user:
836 self._user = user
836 self._user = user
837 if changes:
837 if changes:
838 self._status = list(changes[:4])
838 self._status = list(changes[:4])
839 self._unknown = changes[4]
839 self._unknown = changes[4]
840 self._ignored = changes[5]
840 self._ignored = changes[5]
841 self._clean = changes[6]
841 self._clean = changes[6]
842 else:
842 else:
843 self._unknown = None
843 self._unknown = None
844 self._ignored = None
844 self._ignored = None
845 self._clean = None
845 self._clean = None
846
846
847 self._extra = {}
847 self._extra = {}
848 if extra:
848 if extra:
849 self._extra = extra.copy()
849 self._extra = extra.copy()
850 if 'branch' not in self._extra:
850 if 'branch' not in self._extra:
851 try:
851 try:
852 branch = encoding.fromlocal(self._repo.dirstate.branch())
852 branch = encoding.fromlocal(self._repo.dirstate.branch())
853 except UnicodeDecodeError:
853 except UnicodeDecodeError:
854 raise util.Abort(_('branch name not in UTF-8!'))
854 raise util.Abort(_('branch name not in UTF-8!'))
855 self._extra['branch'] = branch
855 self._extra['branch'] = branch
856 if self._extra['branch'] == '':
856 if self._extra['branch'] == '':
857 self._extra['branch'] = 'default'
857 self._extra['branch'] = 'default'
858
858
859 def __str__(self):
859 def __str__(self):
860 return str(self._parents[0]) + "+"
860 return str(self._parents[0]) + "+"
861
861
862 def __nonzero__(self):
862 def __nonzero__(self):
863 return True
863 return True
864
864
865 def __contains__(self, key):
865 def __contains__(self, key):
866 return self._repo.dirstate[key] not in "?r"
866 return self._repo.dirstate[key] not in "?r"
867
867
868 def _buildflagfunc(self):
868 def _buildflagfunc(self):
869 # Create a fallback function for getting file flags when the
869 # Create a fallback function for getting file flags when the
870 # filesystem doesn't support them
870 # filesystem doesn't support them
871
871
872 copiesget = self._repo.dirstate.copies().get
872 copiesget = self._repo.dirstate.copies().get
873
873
874 if len(self._parents) < 2:
874 if len(self._parents) < 2:
875 # when we have one parent, it's easy: copy from parent
875 # when we have one parent, it's easy: copy from parent
876 man = self._parents[0].manifest()
876 man = self._parents[0].manifest()
877 def func(f):
877 def func(f):
878 f = copiesget(f, f)
878 f = copiesget(f, f)
879 return man.flags(f)
879 return man.flags(f)
880 else:
880 else:
881 # merges are tricky: we try to reconstruct the unstored
881 # merges are tricky: we try to reconstruct the unstored
882 # result from the merge (issue1802)
882 # result from the merge (issue1802)
883 p1, p2 = self._parents
883 p1, p2 = self._parents
884 pa = p1.ancestor(p2)
884 pa = p1.ancestor(p2)
885 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
885 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
886
886
887 def func(f):
887 def func(f):
888 f = copiesget(f, f) # may be wrong for merges with copies
888 f = copiesget(f, f) # may be wrong for merges with copies
889 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
889 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
890 if fl1 == fl2:
890 if fl1 == fl2:
891 return fl1
891 return fl1
892 if fl1 == fla:
892 if fl1 == fla:
893 return fl2
893 return fl2
894 if fl2 == fla:
894 if fl2 == fla:
895 return fl1
895 return fl1
896 return '' # punt for conflicts
896 return '' # punt for conflicts
897
897
898 return func
898 return func
899
899
900 @propertycache
900 @propertycache
901 def _flagfunc(self):
901 def _flagfunc(self):
902 return self._repo.dirstate.flagfunc(self._buildflagfunc)
902 return self._repo.dirstate.flagfunc(self._buildflagfunc)
903
903
904 @propertycache
904 @propertycache
905 def _manifest(self):
905 def _manifest(self):
906 """generate a manifest corresponding to the working directory"""
906 """generate a manifest corresponding to the working directory"""
907
907
908 man = self._parents[0].manifest().copy()
908 man = self._parents[0].manifest().copy()
909 if len(self._parents) > 1:
909 if len(self._parents) > 1:
910 man2 = self.p2().manifest()
910 man2 = self.p2().manifest()
911 def getman(f):
911 def getman(f):
912 if f in man:
912 if f in man:
913 return man
913 return man
914 return man2
914 return man2
915 else:
915 else:
916 getman = lambda f: man
916 getman = lambda f: man
917
917
918 copied = self._repo.dirstate.copies()
918 copied = self._repo.dirstate.copies()
919 ff = self._flagfunc
919 ff = self._flagfunc
920 modified, added, removed, deleted = self._status
920 modified, added, removed, deleted = self._status
921 for i, l in (("a", added), ("m", modified)):
921 for i, l in (("a", added), ("m", modified)):
922 for f in l:
922 for f in l:
923 orig = copied.get(f, f)
923 orig = copied.get(f, f)
924 man[f] = getman(orig).get(orig, nullid) + i
924 man[f] = getman(orig).get(orig, nullid) + i
925 try:
925 try:
926 man.set(f, ff(f))
926 man.set(f, ff(f))
927 except OSError:
927 except OSError:
928 pass
928 pass
929
929
930 for f in deleted + removed:
930 for f in deleted + removed:
931 if f in man:
931 if f in man:
932 del man[f]
932 del man[f]
933
933
934 return man
934 return man
935
935
936 @propertycache
936 @propertycache
937 def _status(self):
937 def _status(self):
938 return self._repo.status()[:4]
938 return self._repo.status()[:4]
939
939
940 @propertycache
941 def _user(self):
942 return self._repo.ui.username()
943
940 def status(self, ignored=False, clean=False, unknown=False):
944 def status(self, ignored=False, clean=False, unknown=False):
941 """Explicit status query
945 """Explicit status query
942 Unless this method is used to query the working copy status, the
946 Unless this method is used to query the working copy status, the
943 _status property will implicitly read the status using its default
947 _status property will implicitly read the status using its default
944 arguments."""
948 arguments."""
945 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
949 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
946 self._unknown = self._ignored = self._clean = None
950 self._unknown = self._ignored = self._clean = None
947 if unknown:
951 if unknown:
948 self._unknown = stat[4]
952 self._unknown = stat[4]
949 if ignored:
953 if ignored:
950 self._ignored = stat[5]
954 self._ignored = stat[5]
951 if clean:
955 if clean:
952 self._clean = stat[6]
956 self._clean = stat[6]
953 self._status = stat[:4]
957 self._status = stat[:4]
954 return stat
958 return stat
955
959
956 class workingctx(commitablectx):
960 class workingctx(commitablectx):
957 """A workingctx object makes access to data related to
961 """A workingctx object makes access to data related to
958 the current working directory convenient.
962 the current working directory convenient.
959 date - any valid date string or (unixtime, offset), or None.
963 date - any valid date string or (unixtime, offset), or None.
960 user - username string, or None.
964 user - username string, or None.
961 extra - a dictionary of extra values, or None.
965 extra - a dictionary of extra values, or None.
962 changes - a list of file lists as returned by localrepo.status()
966 changes - a list of file lists as returned by localrepo.status()
963 or None to use the repository status.
967 or None to use the repository status.
964 """
968 """
965 def __init__(self, repo, text="", user=None, date=None, extra=None,
969 def __init__(self, repo, text="", user=None, date=None, extra=None,
966 changes=None):
970 changes=None):
967 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
971 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
968
972
969 def __iter__(self):
973 def __iter__(self):
970 d = self._repo.dirstate
974 d = self._repo.dirstate
971 for f in d:
975 for f in d:
972 if d[f] != 'r':
976 if d[f] != 'r':
973 yield f
977 yield f
974
978
975 @propertycache
979 @propertycache
976 def _user(self):
977 return self._repo.ui.username()
978
979 @propertycache
980 def _date(self):
980 def _date(self):
981 return util.makedate()
981 return util.makedate()
982
982
983 @propertycache
983 @propertycache
984 def _parents(self):
984 def _parents(self):
985 p = self._repo.dirstate.parents()
985 p = self._repo.dirstate.parents()
986 if p[1] == nullid:
986 if p[1] == nullid:
987 p = p[:-1]
987 p = p[:-1]
988 return [changectx(self._repo, x) for x in p]
988 return [changectx(self._repo, x) for x in p]
989
989
990 def user(self):
990 def user(self):
991 return self._user or self._repo.ui.username()
991 return self._user or self._repo.ui.username()
992 def date(self):
992 def date(self):
993 return self._date
993 return self._date
994 def description(self):
994 def description(self):
995 return self._text
995 return self._text
996 def files(self):
996 def files(self):
997 return sorted(self._status[0] + self._status[1] + self._status[2])
997 return sorted(self._status[0] + self._status[1] + self._status[2])
998
998
999 def modified(self):
999 def modified(self):
1000 return self._status[0]
1000 return self._status[0]
1001 def added(self):
1001 def added(self):
1002 return self._status[1]
1002 return self._status[1]
1003 def removed(self):
1003 def removed(self):
1004 return self._status[2]
1004 return self._status[2]
1005 def deleted(self):
1005 def deleted(self):
1006 return self._status[3]
1006 return self._status[3]
1007 def unknown(self):
1007 def unknown(self):
1008 assert self._unknown is not None # must call status first
1008 assert self._unknown is not None # must call status first
1009 return self._unknown
1009 return self._unknown
1010 def ignored(self):
1010 def ignored(self):
1011 assert self._ignored is not None # must call status first
1011 assert self._ignored is not None # must call status first
1012 return self._ignored
1012 return self._ignored
1013 def clean(self):
1013 def clean(self):
1014 assert self._clean is not None # must call status first
1014 assert self._clean is not None # must call status first
1015 return self._clean
1015 return self._clean
1016 def branch(self):
1016 def branch(self):
1017 return encoding.tolocal(self._extra['branch'])
1017 return encoding.tolocal(self._extra['branch'])
1018 def closesbranch(self):
1018 def closesbranch(self):
1019 return 'close' in self._extra
1019 return 'close' in self._extra
1020 def extra(self):
1020 def extra(self):
1021 return self._extra
1021 return self._extra
1022
1022
1023 def tags(self):
1023 def tags(self):
1024 t = []
1024 t = []
1025 for p in self.parents():
1025 for p in self.parents():
1026 t.extend(p.tags())
1026 t.extend(p.tags())
1027 return t
1027 return t
1028
1028
1029 def bookmarks(self):
1029 def bookmarks(self):
1030 b = []
1030 b = []
1031 for p in self.parents():
1031 for p in self.parents():
1032 b.extend(p.bookmarks())
1032 b.extend(p.bookmarks())
1033 return b
1033 return b
1034
1034
1035 def phase(self):
1035 def phase(self):
1036 phase = phases.draft # default phase to draft
1036 phase = phases.draft # default phase to draft
1037 for p in self.parents():
1037 for p in self.parents():
1038 phase = max(phase, p.phase())
1038 phase = max(phase, p.phase())
1039 return phase
1039 return phase
1040
1040
1041 def hidden(self):
1041 def hidden(self):
1042 return False
1042 return False
1043
1043
1044 def children(self):
1044 def children(self):
1045 return []
1045 return []
1046
1046
1047 def flags(self, path):
1047 def flags(self, path):
1048 if '_manifest' in self.__dict__:
1048 if '_manifest' in self.__dict__:
1049 try:
1049 try:
1050 return self._manifest.flags(path)
1050 return self._manifest.flags(path)
1051 except KeyError:
1051 except KeyError:
1052 return ''
1052 return ''
1053
1053
1054 try:
1054 try:
1055 return self._flagfunc(path)
1055 return self._flagfunc(path)
1056 except OSError:
1056 except OSError:
1057 return ''
1057 return ''
1058
1058
1059 def filectx(self, path, filelog=None):
1059 def filectx(self, path, filelog=None):
1060 """get a file context from the working directory"""
1060 """get a file context from the working directory"""
1061 return workingfilectx(self._repo, path, workingctx=self,
1061 return workingfilectx(self._repo, path, workingctx=self,
1062 filelog=filelog)
1062 filelog=filelog)
1063
1063
1064 def ancestor(self, c2):
1064 def ancestor(self, c2):
1065 """return the ancestor context of self and c2"""
1065 """return the ancestor context of self and c2"""
1066 return self._parents[0].ancestor(c2) # punt on two parents for now
1066 return self._parents[0].ancestor(c2) # punt on two parents for now
1067
1067
1068 def walk(self, match):
1068 def walk(self, match):
1069 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1069 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1070 True, False))
1070 True, False))
1071
1071
1072 def dirty(self, missing=False, merge=True, branch=True):
1072 def dirty(self, missing=False, merge=True, branch=True):
1073 "check whether a working directory is modified"
1073 "check whether a working directory is modified"
1074 # check subrepos first
1074 # check subrepos first
1075 for s in sorted(self.substate):
1075 for s in sorted(self.substate):
1076 if self.sub(s).dirty():
1076 if self.sub(s).dirty():
1077 return True
1077 return True
1078 # check current working dir
1078 # check current working dir
1079 return ((merge and self.p2()) or
1079 return ((merge and self.p2()) or
1080 (branch and self.branch() != self.p1().branch()) or
1080 (branch and self.branch() != self.p1().branch()) or
1081 self.modified() or self.added() or self.removed() or
1081 self.modified() or self.added() or self.removed() or
1082 (missing and self.deleted()))
1082 (missing and self.deleted()))
1083
1083
1084 def add(self, list, prefix=""):
1084 def add(self, list, prefix=""):
1085 join = lambda f: os.path.join(prefix, f)
1085 join = lambda f: os.path.join(prefix, f)
1086 wlock = self._repo.wlock()
1086 wlock = self._repo.wlock()
1087 ui, ds = self._repo.ui, self._repo.dirstate
1087 ui, ds = self._repo.ui, self._repo.dirstate
1088 try:
1088 try:
1089 rejected = []
1089 rejected = []
1090 for f in list:
1090 for f in list:
1091 scmutil.checkportable(ui, join(f))
1091 scmutil.checkportable(ui, join(f))
1092 p = self._repo.wjoin(f)
1092 p = self._repo.wjoin(f)
1093 try:
1093 try:
1094 st = os.lstat(p)
1094 st = os.lstat(p)
1095 except OSError:
1095 except OSError:
1096 ui.warn(_("%s does not exist!\n") % join(f))
1096 ui.warn(_("%s does not exist!\n") % join(f))
1097 rejected.append(f)
1097 rejected.append(f)
1098 continue
1098 continue
1099 if st.st_size > 10000000:
1099 if st.st_size > 10000000:
1100 ui.warn(_("%s: up to %d MB of RAM may be required "
1100 ui.warn(_("%s: up to %d MB of RAM may be required "
1101 "to manage this file\n"
1101 "to manage this file\n"
1102 "(use 'hg revert %s' to cancel the "
1102 "(use 'hg revert %s' to cancel the "
1103 "pending addition)\n")
1103 "pending addition)\n")
1104 % (f, 3 * st.st_size // 1000000, join(f)))
1104 % (f, 3 * st.st_size // 1000000, join(f)))
1105 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1105 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1106 ui.warn(_("%s not added: only files and symlinks "
1106 ui.warn(_("%s not added: only files and symlinks "
1107 "supported currently\n") % join(f))
1107 "supported currently\n") % join(f))
1108 rejected.append(p)
1108 rejected.append(p)
1109 elif ds[f] in 'amn':
1109 elif ds[f] in 'amn':
1110 ui.warn(_("%s already tracked!\n") % join(f))
1110 ui.warn(_("%s already tracked!\n") % join(f))
1111 elif ds[f] == 'r':
1111 elif ds[f] == 'r':
1112 ds.normallookup(f)
1112 ds.normallookup(f)
1113 else:
1113 else:
1114 ds.add(f)
1114 ds.add(f)
1115 return rejected
1115 return rejected
1116 finally:
1116 finally:
1117 wlock.release()
1117 wlock.release()
1118
1118
1119 def forget(self, files, prefix=""):
1119 def forget(self, files, prefix=""):
1120 join = lambda f: os.path.join(prefix, f)
1120 join = lambda f: os.path.join(prefix, f)
1121 wlock = self._repo.wlock()
1121 wlock = self._repo.wlock()
1122 try:
1122 try:
1123 rejected = []
1123 rejected = []
1124 for f in files:
1124 for f in files:
1125 if f not in self._repo.dirstate:
1125 if f not in self._repo.dirstate:
1126 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1126 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1127 rejected.append(f)
1127 rejected.append(f)
1128 elif self._repo.dirstate[f] != 'a':
1128 elif self._repo.dirstate[f] != 'a':
1129 self._repo.dirstate.remove(f)
1129 self._repo.dirstate.remove(f)
1130 else:
1130 else:
1131 self._repo.dirstate.drop(f)
1131 self._repo.dirstate.drop(f)
1132 return rejected
1132 return rejected
1133 finally:
1133 finally:
1134 wlock.release()
1134 wlock.release()
1135
1135
1136 def ancestors(self):
1136 def ancestors(self):
1137 for a in self._repo.changelog.ancestors(
1137 for a in self._repo.changelog.ancestors(
1138 [p.rev() for p in self._parents]):
1138 [p.rev() for p in self._parents]):
1139 yield changectx(self._repo, a)
1139 yield changectx(self._repo, a)
1140
1140
1141 def undelete(self, list):
1141 def undelete(self, list):
1142 pctxs = self.parents()
1142 pctxs = self.parents()
1143 wlock = self._repo.wlock()
1143 wlock = self._repo.wlock()
1144 try:
1144 try:
1145 for f in list:
1145 for f in list:
1146 if self._repo.dirstate[f] != 'r':
1146 if self._repo.dirstate[f] != 'r':
1147 self._repo.ui.warn(_("%s not removed!\n") % f)
1147 self._repo.ui.warn(_("%s not removed!\n") % f)
1148 else:
1148 else:
1149 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1149 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1150 t = fctx.data()
1150 t = fctx.data()
1151 self._repo.wwrite(f, t, fctx.flags())
1151 self._repo.wwrite(f, t, fctx.flags())
1152 self._repo.dirstate.normal(f)
1152 self._repo.dirstate.normal(f)
1153 finally:
1153 finally:
1154 wlock.release()
1154 wlock.release()
1155
1155
1156 def copy(self, source, dest):
1156 def copy(self, source, dest):
1157 p = self._repo.wjoin(dest)
1157 p = self._repo.wjoin(dest)
1158 if not os.path.lexists(p):
1158 if not os.path.lexists(p):
1159 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1159 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1160 elif not (os.path.isfile(p) or os.path.islink(p)):
1160 elif not (os.path.isfile(p) or os.path.islink(p)):
1161 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1161 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1162 "symbolic link\n") % dest)
1162 "symbolic link\n") % dest)
1163 else:
1163 else:
1164 wlock = self._repo.wlock()
1164 wlock = self._repo.wlock()
1165 try:
1165 try:
1166 if self._repo.dirstate[dest] in '?r':
1166 if self._repo.dirstate[dest] in '?r':
1167 self._repo.dirstate.add(dest)
1167 self._repo.dirstate.add(dest)
1168 self._repo.dirstate.copy(source, dest)
1168 self._repo.dirstate.copy(source, dest)
1169 finally:
1169 finally:
1170 wlock.release()
1170 wlock.release()
1171
1171
1172 def markcommitted(self, node):
1172 def markcommitted(self, node):
1173 """Perform post-commit cleanup necessary after committing this ctx
1173 """Perform post-commit cleanup necessary after committing this ctx
1174
1174
1175 Specifically, this updates backing stores this working context
1175 Specifically, this updates backing stores this working context
1176 wraps to reflect the fact that the changes reflected by this
1176 wraps to reflect the fact that the changes reflected by this
1177 workingctx have been committed. For example, it marks
1177 workingctx have been committed. For example, it marks
1178 modified and added files as normal in the dirstate.
1178 modified and added files as normal in the dirstate.
1179
1179
1180 """
1180 """
1181
1181
1182 for f in self.modified() + self.added():
1182 for f in self.modified() + self.added():
1183 self._repo.dirstate.normal(f)
1183 self._repo.dirstate.normal(f)
1184 for f in self.removed():
1184 for f in self.removed():
1185 self._repo.dirstate.drop(f)
1185 self._repo.dirstate.drop(f)
1186 self._repo.dirstate.setparents(node)
1186 self._repo.dirstate.setparents(node)
1187
1187
1188 def dirs(self):
1188 def dirs(self):
1189 return self._repo.dirstate.dirs()
1189 return self._repo.dirstate.dirs()
1190
1190
1191 class workingfilectx(basefilectx):
1191 class workingfilectx(basefilectx):
1192 """A workingfilectx object makes access to data related to a particular
1192 """A workingfilectx object makes access to data related to a particular
1193 file in the working directory convenient."""
1193 file in the working directory convenient."""
1194 def __init__(self, repo, path, filelog=None, workingctx=None):
1194 def __init__(self, repo, path, filelog=None, workingctx=None):
1195 self._repo = repo
1195 self._repo = repo
1196 self._path = path
1196 self._path = path
1197 self._changeid = None
1197 self._changeid = None
1198 self._filerev = self._filenode = None
1198 self._filerev = self._filenode = None
1199
1199
1200 if filelog is not None:
1200 if filelog is not None:
1201 self._filelog = filelog
1201 self._filelog = filelog
1202 if workingctx:
1202 if workingctx:
1203 self._changectx = workingctx
1203 self._changectx = workingctx
1204
1204
1205 @propertycache
1205 @propertycache
1206 def _changectx(self):
1206 def _changectx(self):
1207 return workingctx(self._repo)
1207 return workingctx(self._repo)
1208
1208
1209 def __nonzero__(self):
1209 def __nonzero__(self):
1210 return True
1210 return True
1211
1211
1212 def data(self):
1212 def data(self):
1213 return self._repo.wread(self._path)
1213 return self._repo.wread(self._path)
1214 def renamed(self):
1214 def renamed(self):
1215 rp = self._repo.dirstate.copied(self._path)
1215 rp = self._repo.dirstate.copied(self._path)
1216 if not rp:
1216 if not rp:
1217 return None
1217 return None
1218 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1218 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1219
1219
1220 def parents(self):
1220 def parents(self):
1221 '''return parent filectxs, following copies if necessary'''
1221 '''return parent filectxs, following copies if necessary'''
1222 def filenode(ctx, path):
1222 def filenode(ctx, path):
1223 return ctx._manifest.get(path, nullid)
1223 return ctx._manifest.get(path, nullid)
1224
1224
1225 path = self._path
1225 path = self._path
1226 fl = self._filelog
1226 fl = self._filelog
1227 pcl = self._changectx._parents
1227 pcl = self._changectx._parents
1228 renamed = self.renamed()
1228 renamed = self.renamed()
1229
1229
1230 if renamed:
1230 if renamed:
1231 pl = [renamed + (None,)]
1231 pl = [renamed + (None,)]
1232 else:
1232 else:
1233 pl = [(path, filenode(pcl[0], path), fl)]
1233 pl = [(path, filenode(pcl[0], path), fl)]
1234
1234
1235 for pc in pcl[1:]:
1235 for pc in pcl[1:]:
1236 pl.append((path, filenode(pc, path), fl))
1236 pl.append((path, filenode(pc, path), fl))
1237
1237
1238 return [filectx(self._repo, p, fileid=n, filelog=l)
1238 return [filectx(self._repo, p, fileid=n, filelog=l)
1239 for p, n, l in pl if n != nullid]
1239 for p, n, l in pl if n != nullid]
1240
1240
1241 def children(self):
1241 def children(self):
1242 return []
1242 return []
1243
1243
1244 def size(self):
1244 def size(self):
1245 return os.lstat(self._repo.wjoin(self._path)).st_size
1245 return os.lstat(self._repo.wjoin(self._path)).st_size
1246 def date(self):
1246 def date(self):
1247 t, tz = self._changectx.date()
1247 t, tz = self._changectx.date()
1248 try:
1248 try:
1249 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1249 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1250 except OSError, err:
1250 except OSError, err:
1251 if err.errno != errno.ENOENT:
1251 if err.errno != errno.ENOENT:
1252 raise
1252 raise
1253 return (t, tz)
1253 return (t, tz)
1254
1254
1255 def cmp(self, fctx):
1255 def cmp(self, fctx):
1256 """compare with other file context
1256 """compare with other file context
1257
1257
1258 returns True if different than fctx.
1258 returns True if different than fctx.
1259 """
1259 """
1260 # fctx should be a filectx (not a workingfilectx)
1260 # fctx should be a filectx (not a workingfilectx)
1261 # invert comparison to reuse the same code path
1261 # invert comparison to reuse the same code path
1262 return fctx.cmp(self)
1262 return fctx.cmp(self)
1263
1263
1264 class memctx(object):
1264 class memctx(object):
1265 """Use memctx to perform in-memory commits via localrepo.commitctx().
1265 """Use memctx to perform in-memory commits via localrepo.commitctx().
1266
1266
1267 Revision information is supplied at initialization time while
1267 Revision information is supplied at initialization time while
1268 related files data and is made available through a callback
1268 related files data and is made available through a callback
1269 mechanism. 'repo' is the current localrepo, 'parents' is a
1269 mechanism. 'repo' is the current localrepo, 'parents' is a
1270 sequence of two parent revisions identifiers (pass None for every
1270 sequence of two parent revisions identifiers (pass None for every
1271 missing parent), 'text' is the commit message and 'files' lists
1271 missing parent), 'text' is the commit message and 'files' lists
1272 names of files touched by the revision (normalized and relative to
1272 names of files touched by the revision (normalized and relative to
1273 repository root).
1273 repository root).
1274
1274
1275 filectxfn(repo, memctx, path) is a callable receiving the
1275 filectxfn(repo, memctx, path) is a callable receiving the
1276 repository, the current memctx object and the normalized path of
1276 repository, the current memctx object and the normalized path of
1277 requested file, relative to repository root. It is fired by the
1277 requested file, relative to repository root. It is fired by the
1278 commit function for every file in 'files', but calls order is
1278 commit function for every file in 'files', but calls order is
1279 undefined. If the file is available in the revision being
1279 undefined. If the file is available in the revision being
1280 committed (updated or added), filectxfn returns a memfilectx
1280 committed (updated or added), filectxfn returns a memfilectx
1281 object. If the file was removed, filectxfn raises an
1281 object. If the file was removed, filectxfn raises an
1282 IOError. Moved files are represented by marking the source file
1282 IOError. Moved files are represented by marking the source file
1283 removed and the new file added with copy information (see
1283 removed and the new file added with copy information (see
1284 memfilectx).
1284 memfilectx).
1285
1285
1286 user receives the committer name and defaults to current
1286 user receives the committer name and defaults to current
1287 repository username, date is the commit date in any format
1287 repository username, date is the commit date in any format
1288 supported by util.parsedate() and defaults to current date, extra
1288 supported by util.parsedate() and defaults to current date, extra
1289 is a dictionary of metadata or is left empty.
1289 is a dictionary of metadata or is left empty.
1290 """
1290 """
1291 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1291 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1292 date=None, extra=None):
1292 date=None, extra=None):
1293 self._repo = repo
1293 self._repo = repo
1294 self._rev = None
1294 self._rev = None
1295 self._node = None
1295 self._node = None
1296 self._text = text
1296 self._text = text
1297 self._date = date and util.parsedate(date) or util.makedate()
1297 self._date = date and util.parsedate(date) or util.makedate()
1298 self._user = user
1298 self._user = user
1299 parents = [(p or nullid) for p in parents]
1299 parents = [(p or nullid) for p in parents]
1300 p1, p2 = parents
1300 p1, p2 = parents
1301 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1301 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1302 files = sorted(set(files))
1302 files = sorted(set(files))
1303 self._status = [files, [], [], [], []]
1303 self._status = [files, [], [], [], []]
1304 self._filectxfn = filectxfn
1304 self._filectxfn = filectxfn
1305
1305
1306 self._extra = extra and extra.copy() or {}
1306 self._extra = extra and extra.copy() or {}
1307 if self._extra.get('branch', '') == '':
1307 if self._extra.get('branch', '') == '':
1308 self._extra['branch'] = 'default'
1308 self._extra['branch'] = 'default'
1309
1309
1310 def __str__(self):
1310 def __str__(self):
1311 return str(self._parents[0]) + "+"
1311 return str(self._parents[0]) + "+"
1312
1312
1313 def __int__(self):
1313 def __int__(self):
1314 return self._rev
1314 return self._rev
1315
1315
1316 def __nonzero__(self):
1316 def __nonzero__(self):
1317 return True
1317 return True
1318
1318
1319 def __getitem__(self, key):
1319 def __getitem__(self, key):
1320 return self.filectx(key)
1320 return self.filectx(key)
1321
1321
1322 def p1(self):
1322 def p1(self):
1323 return self._parents[0]
1323 return self._parents[0]
1324 def p2(self):
1324 def p2(self):
1325 return self._parents[1]
1325 return self._parents[1]
1326
1326
1327 def user(self):
1327 def user(self):
1328 return self._user or self._repo.ui.username()
1328 return self._user or self._repo.ui.username()
1329 def date(self):
1329 def date(self):
1330 return self._date
1330 return self._date
1331 def description(self):
1331 def description(self):
1332 return self._text
1332 return self._text
1333 def files(self):
1333 def files(self):
1334 return self.modified()
1334 return self.modified()
1335 def modified(self):
1335 def modified(self):
1336 return self._status[0]
1336 return self._status[0]
1337 def added(self):
1337 def added(self):
1338 return self._status[1]
1338 return self._status[1]
1339 def removed(self):
1339 def removed(self):
1340 return self._status[2]
1340 return self._status[2]
1341 def deleted(self):
1341 def deleted(self):
1342 return self._status[3]
1342 return self._status[3]
1343 def unknown(self):
1343 def unknown(self):
1344 return self._status[4]
1344 return self._status[4]
1345 def ignored(self):
1345 def ignored(self):
1346 return self._status[5]
1346 return self._status[5]
1347 def clean(self):
1347 def clean(self):
1348 return self._status[6]
1348 return self._status[6]
1349 def branch(self):
1349 def branch(self):
1350 return encoding.tolocal(self._extra['branch'])
1350 return encoding.tolocal(self._extra['branch'])
1351 def extra(self):
1351 def extra(self):
1352 return self._extra
1352 return self._extra
1353 def flags(self, f):
1353 def flags(self, f):
1354 return self[f].flags()
1354 return self[f].flags()
1355
1355
1356 def parents(self):
1356 def parents(self):
1357 """return contexts for each parent changeset"""
1357 """return contexts for each parent changeset"""
1358 return self._parents
1358 return self._parents
1359
1359
1360 def filectx(self, path, filelog=None):
1360 def filectx(self, path, filelog=None):
1361 """get a file context from the working directory"""
1361 """get a file context from the working directory"""
1362 return self._filectxfn(self._repo, self, path)
1362 return self._filectxfn(self._repo, self, path)
1363
1363
1364 def commit(self):
1364 def commit(self):
1365 """commit context to the repo"""
1365 """commit context to the repo"""
1366 return self._repo.commitctx(self)
1366 return self._repo.commitctx(self)
1367
1367
1368 class memfilectx(object):
1368 class memfilectx(object):
1369 """memfilectx represents an in-memory file to commit.
1369 """memfilectx represents an in-memory file to commit.
1370
1370
1371 See memctx for more details.
1371 See memctx for more details.
1372 """
1372 """
1373 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1373 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1374 """
1374 """
1375 path is the normalized file path relative to repository root.
1375 path is the normalized file path relative to repository root.
1376 data is the file content as a string.
1376 data is the file content as a string.
1377 islink is True if the file is a symbolic link.
1377 islink is True if the file is a symbolic link.
1378 isexec is True if the file is executable.
1378 isexec is True if the file is executable.
1379 copied is the source file path if current file was copied in the
1379 copied is the source file path if current file was copied in the
1380 revision being committed, or None."""
1380 revision being committed, or None."""
1381 self._path = path
1381 self._path = path
1382 self._data = data
1382 self._data = data
1383 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1383 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1384 self._copied = None
1384 self._copied = None
1385 if copied:
1385 if copied:
1386 self._copied = (copied, nullid)
1386 self._copied = (copied, nullid)
1387
1387
1388 def __nonzero__(self):
1388 def __nonzero__(self):
1389 return True
1389 return True
1390 def __str__(self):
1390 def __str__(self):
1391 return "%s@%s" % (self.path(), self._changectx)
1391 return "%s@%s" % (self.path(), self._changectx)
1392 def path(self):
1392 def path(self):
1393 return self._path
1393 return self._path
1394 def data(self):
1394 def data(self):
1395 return self._data
1395 return self._data
1396 def flags(self):
1396 def flags(self):
1397 return self._flags
1397 return self._flags
1398 def isexec(self):
1398 def isexec(self):
1399 return 'x' in self._flags
1399 return 'x' in self._flags
1400 def islink(self):
1400 def islink(self):
1401 return 'l' in self._flags
1401 return 'l' in self._flags
1402 def renamed(self):
1402 def renamed(self):
1403 return self._copied
1403 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now