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