##// END OF EJS Templates
committablectx: move status to workingctx...
Sean Farley -
r21396:3925d946 default
parent child Browse files
Show More
@@ -1,1455 +1,1455 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 status(self, ignored=False, clean=False, unknown=False):
935 """Explicit status query
936 Unless this method is used to query the working copy status, the
937 _status property will implicitly read the status using its default
938 arguments."""
939 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
940 self._unknown = self._ignored = self._clean = None
941 if unknown:
942 self._unknown = stat[4]
943 if ignored:
944 self._ignored = stat[5]
945 if clean:
946 self._clean = stat[6]
947 self._status = stat[:4]
948 return stat
949
950 def user(self):
934 def user(self):
951 return self._user or self._repo.ui.username()
935 return self._user or self._repo.ui.username()
952 def date(self):
936 def date(self):
953 return self._date
937 return self._date
954 def description(self):
938 def description(self):
955 return self._text
939 return self._text
956 def files(self):
940 def files(self):
957 return sorted(self._status[0] + self._status[1] + self._status[2])
941 return sorted(self._status[0] + self._status[1] + self._status[2])
958
942
959 def modified(self):
943 def modified(self):
960 return self._status[0]
944 return self._status[0]
961 def added(self):
945 def added(self):
962 return self._status[1]
946 return self._status[1]
963 def removed(self):
947 def removed(self):
964 return self._status[2]
948 return self._status[2]
965 def deleted(self):
949 def deleted(self):
966 return self._status[3]
950 return self._status[3]
967 def unknown(self):
951 def unknown(self):
968 assert self._unknown is not None # must call status first
952 assert self._unknown is not None # must call status first
969 return self._unknown
953 return self._unknown
970 def ignored(self):
954 def ignored(self):
971 assert self._ignored is not None # must call status first
955 assert self._ignored is not None # must call status first
972 return self._ignored
956 return self._ignored
973 def clean(self):
957 def clean(self):
974 assert self._clean is not None # must call status first
958 assert self._clean is not None # must call status first
975 return self._clean
959 return self._clean
976 def branch(self):
960 def branch(self):
977 return encoding.tolocal(self._extra['branch'])
961 return encoding.tolocal(self._extra['branch'])
978 def closesbranch(self):
962 def closesbranch(self):
979 return 'close' in self._extra
963 return 'close' in self._extra
980 def extra(self):
964 def extra(self):
981 return self._extra
965 return self._extra
982
966
983 def tags(self):
967 def tags(self):
984 t = []
968 t = []
985 for p in self.parents():
969 for p in self.parents():
986 t.extend(p.tags())
970 t.extend(p.tags())
987 return t
971 return t
988
972
989 def bookmarks(self):
973 def bookmarks(self):
990 b = []
974 b = []
991 for p in self.parents():
975 for p in self.parents():
992 b.extend(p.bookmarks())
976 b.extend(p.bookmarks())
993 return b
977 return b
994
978
995 def phase(self):
979 def phase(self):
996 phase = phases.draft # default phase to draft
980 phase = phases.draft # default phase to draft
997 for p in self.parents():
981 for p in self.parents():
998 phase = max(phase, p.phase())
982 phase = max(phase, p.phase())
999 return phase
983 return phase
1000
984
1001 def hidden(self):
985 def hidden(self):
1002 return False
986 return False
1003
987
1004 def children(self):
988 def children(self):
1005 return []
989 return []
1006
990
1007 def flags(self, path):
991 def flags(self, path):
1008 if '_manifest' in self.__dict__:
992 if '_manifest' in self.__dict__:
1009 try:
993 try:
1010 return self._manifest.flags(path)
994 return self._manifest.flags(path)
1011 except KeyError:
995 except KeyError:
1012 return ''
996 return ''
1013
997
1014 try:
998 try:
1015 return self._flagfunc(path)
999 return self._flagfunc(path)
1016 except OSError:
1000 except OSError:
1017 return ''
1001 return ''
1018
1002
1019 def ancestor(self, c2):
1003 def ancestor(self, c2):
1020 """return the ancestor context of self and c2"""
1004 """return the ancestor context of self and c2"""
1021 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
1022
1006
1023 def walk(self, match):
1007 def walk(self, match):
1024 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1008 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1025 True, False))
1009 True, False))
1026
1010
1027 def ancestors(self):
1011 def ancestors(self):
1028 for a in self._repo.changelog.ancestors(
1012 for a in self._repo.changelog.ancestors(
1029 [p.rev() for p in self._parents]):
1013 [p.rev() for p in self._parents]):
1030 yield changectx(self._repo, a)
1014 yield changectx(self._repo, a)
1031
1015
1032 def markcommitted(self, node):
1016 def markcommitted(self, node):
1033 """Perform post-commit cleanup necessary after committing this ctx
1017 """Perform post-commit cleanup necessary after committing this ctx
1034
1018
1035 Specifically, this updates backing stores this working context
1019 Specifically, this updates backing stores this working context
1036 wraps to reflect the fact that the changes reflected by this
1020 wraps to reflect the fact that the changes reflected by this
1037 workingctx have been committed. For example, it marks
1021 workingctx have been committed. For example, it marks
1038 modified and added files as normal in the dirstate.
1022 modified and added files as normal in the dirstate.
1039
1023
1040 """
1024 """
1041
1025
1042 for f in self.modified() + self.added():
1026 for f in self.modified() + self.added():
1043 self._repo.dirstate.normal(f)
1027 self._repo.dirstate.normal(f)
1044 for f in self.removed():
1028 for f in self.removed():
1045 self._repo.dirstate.drop(f)
1029 self._repo.dirstate.drop(f)
1046 self._repo.dirstate.setparents(node)
1030 self._repo.dirstate.setparents(node)
1047
1031
1048 def dirs(self):
1032 def dirs(self):
1049 return self._repo.dirstate.dirs()
1033 return self._repo.dirstate.dirs()
1050
1034
1051 class workingctx(committablectx):
1035 class workingctx(committablectx):
1052 """A workingctx object makes access to data related to
1036 """A workingctx object makes access to data related to
1053 the current working directory convenient.
1037 the current working directory convenient.
1054 date - any valid date string or (unixtime, offset), or None.
1038 date - any valid date string or (unixtime, offset), or None.
1055 user - username string, or None.
1039 user - username string, or None.
1056 extra - a dictionary of extra values, or None.
1040 extra - a dictionary of extra values, or None.
1057 changes - a list of file lists as returned by localrepo.status()
1041 changes - a list of file lists as returned by localrepo.status()
1058 or None to use the repository status.
1042 or None to use the repository status.
1059 """
1043 """
1060 def __init__(self, repo, text="", user=None, date=None, extra=None,
1044 def __init__(self, repo, text="", user=None, date=None, extra=None,
1061 changes=None):
1045 changes=None):
1062 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1046 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1063
1047
1064 def __iter__(self):
1048 def __iter__(self):
1065 d = self._repo.dirstate
1049 d = self._repo.dirstate
1066 for f in d:
1050 for f in d:
1067 if d[f] != 'r':
1051 if d[f] != 'r':
1068 yield f
1052 yield f
1069
1053
1070 @propertycache
1054 @propertycache
1071 def _parents(self):
1055 def _parents(self):
1072 p = self._repo.dirstate.parents()
1056 p = self._repo.dirstate.parents()
1073 if p[1] == nullid:
1057 if p[1] == nullid:
1074 p = p[:-1]
1058 p = p[:-1]
1075 return [changectx(self._repo, x) for x in p]
1059 return [changectx(self._repo, x) for x in p]
1076
1060
1077 def filectx(self, path, filelog=None):
1061 def filectx(self, path, filelog=None):
1078 """get a file context from the working directory"""
1062 """get a file context from the working directory"""
1079 return workingfilectx(self._repo, path, workingctx=self,
1063 return workingfilectx(self._repo, path, workingctx=self,
1080 filelog=filelog)
1064 filelog=filelog)
1081
1065
1082 def dirty(self, missing=False, merge=True, branch=True):
1066 def dirty(self, missing=False, merge=True, branch=True):
1083 "check whether a working directory is modified"
1067 "check whether a working directory is modified"
1084 # check subrepos first
1068 # check subrepos first
1085 for s in sorted(self.substate):
1069 for s in sorted(self.substate):
1086 if self.sub(s).dirty():
1070 if self.sub(s).dirty():
1087 return True
1071 return True
1088 # check current working dir
1072 # check current working dir
1089 return ((merge and self.p2()) or
1073 return ((merge and self.p2()) or
1090 (branch and self.branch() != self.p1().branch()) or
1074 (branch and self.branch() != self.p1().branch()) or
1091 self.modified() or self.added() or self.removed() or
1075 self.modified() or self.added() or self.removed() or
1092 (missing and self.deleted()))
1076 (missing and self.deleted()))
1093
1077
1094 def add(self, list, prefix=""):
1078 def add(self, list, prefix=""):
1095 join = lambda f: os.path.join(prefix, f)
1079 join = lambda f: os.path.join(prefix, f)
1096 wlock = self._repo.wlock()
1080 wlock = self._repo.wlock()
1097 ui, ds = self._repo.ui, self._repo.dirstate
1081 ui, ds = self._repo.ui, self._repo.dirstate
1098 try:
1082 try:
1099 rejected = []
1083 rejected = []
1100 lstat = self._repo.wvfs.lstat
1084 lstat = self._repo.wvfs.lstat
1101 for f in list:
1085 for f in list:
1102 scmutil.checkportable(ui, join(f))
1086 scmutil.checkportable(ui, join(f))
1103 try:
1087 try:
1104 st = lstat(f)
1088 st = lstat(f)
1105 except OSError:
1089 except OSError:
1106 ui.warn(_("%s does not exist!\n") % join(f))
1090 ui.warn(_("%s does not exist!\n") % join(f))
1107 rejected.append(f)
1091 rejected.append(f)
1108 continue
1092 continue
1109 if st.st_size > 10000000:
1093 if st.st_size > 10000000:
1110 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 "
1111 "to manage this file\n"
1095 "to manage this file\n"
1112 "(use 'hg revert %s' to cancel the "
1096 "(use 'hg revert %s' to cancel the "
1113 "pending addition)\n")
1097 "pending addition)\n")
1114 % (f, 3 * st.st_size // 1000000, join(f)))
1098 % (f, 3 * st.st_size // 1000000, join(f)))
1115 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)):
1116 ui.warn(_("%s not added: only files and symlinks "
1100 ui.warn(_("%s not added: only files and symlinks "
1117 "supported currently\n") % join(f))
1101 "supported currently\n") % join(f))
1118 rejected.append(f)
1102 rejected.append(f)
1119 elif ds[f] in 'amn':
1103 elif ds[f] in 'amn':
1120 ui.warn(_("%s already tracked!\n") % join(f))
1104 ui.warn(_("%s already tracked!\n") % join(f))
1121 elif ds[f] == 'r':
1105 elif ds[f] == 'r':
1122 ds.normallookup(f)
1106 ds.normallookup(f)
1123 else:
1107 else:
1124 ds.add(f)
1108 ds.add(f)
1125 return rejected
1109 return rejected
1126 finally:
1110 finally:
1127 wlock.release()
1111 wlock.release()
1128
1112
1129 def forget(self, files, prefix=""):
1113 def forget(self, files, prefix=""):
1130 join = lambda f: os.path.join(prefix, f)
1114 join = lambda f: os.path.join(prefix, f)
1131 wlock = self._repo.wlock()
1115 wlock = self._repo.wlock()
1132 try:
1116 try:
1133 rejected = []
1117 rejected = []
1134 for f in files:
1118 for f in files:
1135 if f not in self._repo.dirstate:
1119 if f not in self._repo.dirstate:
1136 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1120 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1137 rejected.append(f)
1121 rejected.append(f)
1138 elif self._repo.dirstate[f] != 'a':
1122 elif self._repo.dirstate[f] != 'a':
1139 self._repo.dirstate.remove(f)
1123 self._repo.dirstate.remove(f)
1140 else:
1124 else:
1141 self._repo.dirstate.drop(f)
1125 self._repo.dirstate.drop(f)
1142 return rejected
1126 return rejected
1143 finally:
1127 finally:
1144 wlock.release()
1128 wlock.release()
1145
1129
1146 def undelete(self, list):
1130 def undelete(self, list):
1147 pctxs = self.parents()
1131 pctxs = self.parents()
1148 wlock = self._repo.wlock()
1132 wlock = self._repo.wlock()
1149 try:
1133 try:
1150 for f in list:
1134 for f in list:
1151 if self._repo.dirstate[f] != 'r':
1135 if self._repo.dirstate[f] != 'r':
1152 self._repo.ui.warn(_("%s not removed!\n") % f)
1136 self._repo.ui.warn(_("%s not removed!\n") % f)
1153 else:
1137 else:
1154 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]
1155 t = fctx.data()
1139 t = fctx.data()
1156 self._repo.wwrite(f, t, fctx.flags())
1140 self._repo.wwrite(f, t, fctx.flags())
1157 self._repo.dirstate.normal(f)
1141 self._repo.dirstate.normal(f)
1158 finally:
1142 finally:
1159 wlock.release()
1143 wlock.release()
1160
1144
1161 def copy(self, source, dest):
1145 def copy(self, source, dest):
1162 try:
1146 try:
1163 st = self._repo.wvfs.lstat(dest)
1147 st = self._repo.wvfs.lstat(dest)
1164 except OSError, err:
1148 except OSError, err:
1165 if err.errno != errno.ENOENT:
1149 if err.errno != errno.ENOENT:
1166 raise
1150 raise
1167 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1151 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1168 return
1152 return
1169 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)):
1170 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 "
1171 "symbolic link\n") % dest)
1155 "symbolic link\n") % dest)
1172 else:
1156 else:
1173 wlock = self._repo.wlock()
1157 wlock = self._repo.wlock()
1174 try:
1158 try:
1175 if self._repo.dirstate[dest] in '?r':
1159 if self._repo.dirstate[dest] in '?r':
1176 self._repo.dirstate.add(dest)
1160 self._repo.dirstate.add(dest)
1177 self._repo.dirstate.copy(source, dest)
1161 self._repo.dirstate.copy(source, dest)
1178 finally:
1162 finally:
1179 wlock.release()
1163 wlock.release()
1180
1164
1181 def _filtersuspectsymlink(self, files):
1165 def _filtersuspectsymlink(self, files):
1182 if not files or self._repo.dirstate._checklink:
1166 if not files or self._repo.dirstate._checklink:
1183 return files
1167 return files
1184
1168
1185 # Symlink placeholders may get non-symlink-like contents
1169 # Symlink placeholders may get non-symlink-like contents
1186 # via user error or dereferencing by NFS or Samba servers,
1170 # via user error or dereferencing by NFS or Samba servers,
1187 # 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
1188 # symlink
1172 # symlink
1189 sane = []
1173 sane = []
1190 for f in files:
1174 for f in files:
1191 if self.flags(f) == 'l':
1175 if self.flags(f) == 'l':
1192 d = self[f].data()
1176 d = self[f].data()
1193 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):
1194 self._repo.ui.debug('ignoring suspect symlink placeholder'
1178 self._repo.ui.debug('ignoring suspect symlink placeholder'
1195 ' "%s"\n' % f)
1179 ' "%s"\n' % f)
1196 continue
1180 continue
1197 sane.append(f)
1181 sane.append(f)
1198 return sane
1182 return sane
1199
1183
1200 def _checklookup(self, files):
1184 def _checklookup(self, files):
1201 # check for any possibly clean files
1185 # check for any possibly clean files
1202 if not files:
1186 if not files:
1203 return [], []
1187 return [], []
1204
1188
1205 modified = []
1189 modified = []
1206 fixup = []
1190 fixup = []
1207 pctx = self._parents[0]
1191 pctx = self._parents[0]
1208 # do a full compare of any files that might have changed
1192 # do a full compare of any files that might have changed
1209 for f in sorted(files):
1193 for f in sorted(files):
1210 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)
1211 or pctx[f].cmp(self[f])):
1195 or pctx[f].cmp(self[f])):
1212 modified.append(f)
1196 modified.append(f)
1213 else:
1197 else:
1214 fixup.append(f)
1198 fixup.append(f)
1215
1199
1216 # update dirstate for files that are actually clean
1200 # update dirstate for files that are actually clean
1217 if fixup:
1201 if fixup:
1218 try:
1202 try:
1219 # updating the dirstate is optional
1203 # updating the dirstate is optional
1220 # so we don't wait on the lock
1204 # so we don't wait on the lock
1221 normal = self._repo.dirstate.normal
1205 normal = self._repo.dirstate.normal
1222 wlock = self._repo.wlock(False)
1206 wlock = self._repo.wlock(False)
1223 try:
1207 try:
1224 for f in fixup:
1208 for f in fixup:
1225 normal(f)
1209 normal(f)
1226 finally:
1210 finally:
1227 wlock.release()
1211 wlock.release()
1228 except error.LockError:
1212 except error.LockError:
1229 pass
1213 pass
1230 return modified, fixup
1214 return modified, fixup
1231
1215
1216 def status(self, ignored=False, clean=False, unknown=False):
1217 """Explicit status query
1218 Unless this method is used to query the working copy status, the
1219 _status property will implicitly read the status using its default
1220 arguments."""
1221 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
1222 self._unknown = self._ignored = self._clean = None
1223 if unknown:
1224 self._unknown = stat[4]
1225 if ignored:
1226 self._ignored = stat[5]
1227 if clean:
1228 self._clean = stat[6]
1229 self._status = stat[:4]
1230 return stat
1231
1232
1232
1233 class committablefilectx(basefilectx):
1233 class committablefilectx(basefilectx):
1234 """A committablefilectx provides common functionality for a file context
1234 """A committablefilectx provides common functionality for a file context
1235 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1235 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1236 def __init__(self, repo, path, filelog=None, ctx=None):
1236 def __init__(self, repo, path, filelog=None, ctx=None):
1237 self._repo = repo
1237 self._repo = repo
1238 self._path = path
1238 self._path = path
1239 self._changeid = None
1239 self._changeid = None
1240 self._filerev = self._filenode = None
1240 self._filerev = self._filenode = None
1241
1241
1242 if filelog is not None:
1242 if filelog is not None:
1243 self._filelog = filelog
1243 self._filelog = filelog
1244 if ctx:
1244 if ctx:
1245 self._changectx = ctx
1245 self._changectx = ctx
1246
1246
1247 def __nonzero__(self):
1247 def __nonzero__(self):
1248 return True
1248 return True
1249
1249
1250 def parents(self):
1250 def parents(self):
1251 '''return parent filectxs, following copies if necessary'''
1251 '''return parent filectxs, following copies if necessary'''
1252 def filenode(ctx, path):
1252 def filenode(ctx, path):
1253 return ctx._manifest.get(path, nullid)
1253 return ctx._manifest.get(path, nullid)
1254
1254
1255 path = self._path
1255 path = self._path
1256 fl = self._filelog
1256 fl = self._filelog
1257 pcl = self._changectx._parents
1257 pcl = self._changectx._parents
1258 renamed = self.renamed()
1258 renamed = self.renamed()
1259
1259
1260 if renamed:
1260 if renamed:
1261 pl = [renamed + (None,)]
1261 pl = [renamed + (None,)]
1262 else:
1262 else:
1263 pl = [(path, filenode(pcl[0], path), fl)]
1263 pl = [(path, filenode(pcl[0], path), fl)]
1264
1264
1265 for pc in pcl[1:]:
1265 for pc in pcl[1:]:
1266 pl.append((path, filenode(pc, path), fl))
1266 pl.append((path, filenode(pc, path), fl))
1267
1267
1268 return [filectx(self._repo, p, fileid=n, filelog=l)
1268 return [filectx(self._repo, p, fileid=n, filelog=l)
1269 for p, n, l in pl if n != nullid]
1269 for p, n, l in pl if n != nullid]
1270
1270
1271 def children(self):
1271 def children(self):
1272 return []
1272 return []
1273
1273
1274 class workingfilectx(committablefilectx):
1274 class workingfilectx(committablefilectx):
1275 """A workingfilectx object makes access to data related to a particular
1275 """A workingfilectx object makes access to data related to a particular
1276 file in the working directory convenient."""
1276 file in the working directory convenient."""
1277 def __init__(self, repo, path, filelog=None, workingctx=None):
1277 def __init__(self, repo, path, filelog=None, workingctx=None):
1278 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1278 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1279
1279
1280 @propertycache
1280 @propertycache
1281 def _changectx(self):
1281 def _changectx(self):
1282 return workingctx(self._repo)
1282 return workingctx(self._repo)
1283
1283
1284 def data(self):
1284 def data(self):
1285 return self._repo.wread(self._path)
1285 return self._repo.wread(self._path)
1286 def renamed(self):
1286 def renamed(self):
1287 rp = self._repo.dirstate.copied(self._path)
1287 rp = self._repo.dirstate.copied(self._path)
1288 if not rp:
1288 if not rp:
1289 return None
1289 return None
1290 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1290 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1291
1291
1292 def size(self):
1292 def size(self):
1293 return self._repo.wvfs.lstat(self._path).st_size
1293 return self._repo.wvfs.lstat(self._path).st_size
1294 def date(self):
1294 def date(self):
1295 t, tz = self._changectx.date()
1295 t, tz = self._changectx.date()
1296 try:
1296 try:
1297 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1297 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1298 except OSError, err:
1298 except OSError, err:
1299 if err.errno != errno.ENOENT:
1299 if err.errno != errno.ENOENT:
1300 raise
1300 raise
1301 return (t, tz)
1301 return (t, tz)
1302
1302
1303 def cmp(self, fctx):
1303 def cmp(self, fctx):
1304 """compare with other file context
1304 """compare with other file context
1305
1305
1306 returns True if different than fctx.
1306 returns True if different than fctx.
1307 """
1307 """
1308 # fctx should be a filectx (not a workingfilectx)
1308 # fctx should be a filectx (not a workingfilectx)
1309 # invert comparison to reuse the same code path
1309 # invert comparison to reuse the same code path
1310 return fctx.cmp(self)
1310 return fctx.cmp(self)
1311
1311
1312 class memctx(object):
1312 class memctx(object):
1313 """Use memctx to perform in-memory commits via localrepo.commitctx().
1313 """Use memctx to perform in-memory commits via localrepo.commitctx().
1314
1314
1315 Revision information is supplied at initialization time while
1315 Revision information is supplied at initialization time while
1316 related files data and is made available through a callback
1316 related files data and is made available through a callback
1317 mechanism. 'repo' is the current localrepo, 'parents' is a
1317 mechanism. 'repo' is the current localrepo, 'parents' is a
1318 sequence of two parent revisions identifiers (pass None for every
1318 sequence of two parent revisions identifiers (pass None for every
1319 missing parent), 'text' is the commit message and 'files' lists
1319 missing parent), 'text' is the commit message and 'files' lists
1320 names of files touched by the revision (normalized and relative to
1320 names of files touched by the revision (normalized and relative to
1321 repository root).
1321 repository root).
1322
1322
1323 filectxfn(repo, memctx, path) is a callable receiving the
1323 filectxfn(repo, memctx, path) is a callable receiving the
1324 repository, the current memctx object and the normalized path of
1324 repository, the current memctx object and the normalized path of
1325 requested file, relative to repository root. It is fired by the
1325 requested file, relative to repository root. It is fired by the
1326 commit function for every file in 'files', but calls order is
1326 commit function for every file in 'files', but calls order is
1327 undefined. If the file is available in the revision being
1327 undefined. If the file is available in the revision being
1328 committed (updated or added), filectxfn returns a memfilectx
1328 committed (updated or added), filectxfn returns a memfilectx
1329 object. If the file was removed, filectxfn raises an
1329 object. If the file was removed, filectxfn raises an
1330 IOError. Moved files are represented by marking the source file
1330 IOError. Moved files are represented by marking the source file
1331 removed and the new file added with copy information (see
1331 removed and the new file added with copy information (see
1332 memfilectx).
1332 memfilectx).
1333
1333
1334 user receives the committer name and defaults to current
1334 user receives the committer name and defaults to current
1335 repository username, date is the commit date in any format
1335 repository username, date is the commit date in any format
1336 supported by util.parsedate() and defaults to current date, extra
1336 supported by util.parsedate() and defaults to current date, extra
1337 is a dictionary of metadata or is left empty.
1337 is a dictionary of metadata or is left empty.
1338 """
1338 """
1339 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1339 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1340 date=None, extra=None, editor=False):
1340 date=None, extra=None, editor=False):
1341 self._repo = repo
1341 self._repo = repo
1342 self._rev = None
1342 self._rev = None
1343 self._node = None
1343 self._node = None
1344 self._text = text
1344 self._text = text
1345 self._date = date and util.parsedate(date) or util.makedate()
1345 self._date = date and util.parsedate(date) or util.makedate()
1346 self._user = user
1346 self._user = user
1347 parents = [(p or nullid) for p in parents]
1347 parents = [(p or nullid) for p in parents]
1348 p1, p2 = parents
1348 p1, p2 = parents
1349 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1349 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1350 files = sorted(set(files))
1350 files = sorted(set(files))
1351 self._status = [files, [], [], [], []]
1351 self._status = [files, [], [], [], []]
1352 self._filectxfn = filectxfn
1352 self._filectxfn = filectxfn
1353
1353
1354 self._extra = extra and extra.copy() or {}
1354 self._extra = extra and extra.copy() or {}
1355 if self._extra.get('branch', '') == '':
1355 if self._extra.get('branch', '') == '':
1356 self._extra['branch'] = 'default'
1356 self._extra['branch'] = 'default'
1357
1357
1358 if editor:
1358 if editor:
1359 self._text = editor(self._repo, self, [])
1359 self._text = editor(self._repo, self, [])
1360 self._repo.savecommitmessage(self._text)
1360 self._repo.savecommitmessage(self._text)
1361
1361
1362 def __str__(self):
1362 def __str__(self):
1363 return str(self._parents[0]) + "+"
1363 return str(self._parents[0]) + "+"
1364
1364
1365 def __int__(self):
1365 def __int__(self):
1366 return self._rev
1366 return self._rev
1367
1367
1368 def __nonzero__(self):
1368 def __nonzero__(self):
1369 return True
1369 return True
1370
1370
1371 def __getitem__(self, key):
1371 def __getitem__(self, key):
1372 return self.filectx(key)
1372 return self.filectx(key)
1373
1373
1374 def p1(self):
1374 def p1(self):
1375 return self._parents[0]
1375 return self._parents[0]
1376 def p2(self):
1376 def p2(self):
1377 return self._parents[1]
1377 return self._parents[1]
1378
1378
1379 def user(self):
1379 def user(self):
1380 return self._user or self._repo.ui.username()
1380 return self._user or self._repo.ui.username()
1381 def date(self):
1381 def date(self):
1382 return self._date
1382 return self._date
1383 def description(self):
1383 def description(self):
1384 return self._text
1384 return self._text
1385 def files(self):
1385 def files(self):
1386 return self.modified()
1386 return self.modified()
1387 def modified(self):
1387 def modified(self):
1388 return self._status[0]
1388 return self._status[0]
1389 def added(self):
1389 def added(self):
1390 return self._status[1]
1390 return self._status[1]
1391 def removed(self):
1391 def removed(self):
1392 return self._status[2]
1392 return self._status[2]
1393 def deleted(self):
1393 def deleted(self):
1394 return self._status[3]
1394 return self._status[3]
1395 def unknown(self):
1395 def unknown(self):
1396 return self._status[4]
1396 return self._status[4]
1397 def ignored(self):
1397 def ignored(self):
1398 return self._status[5]
1398 return self._status[5]
1399 def clean(self):
1399 def clean(self):
1400 return self._status[6]
1400 return self._status[6]
1401 def branch(self):
1401 def branch(self):
1402 return encoding.tolocal(self._extra['branch'])
1402 return encoding.tolocal(self._extra['branch'])
1403 def extra(self):
1403 def extra(self):
1404 return self._extra
1404 return self._extra
1405 def flags(self, f):
1405 def flags(self, f):
1406 return self[f].flags()
1406 return self[f].flags()
1407
1407
1408 def parents(self):
1408 def parents(self):
1409 """return contexts for each parent changeset"""
1409 """return contexts for each parent changeset"""
1410 return self._parents
1410 return self._parents
1411
1411
1412 def filectx(self, path, filelog=None):
1412 def filectx(self, path, filelog=None):
1413 """get a file context from the working directory"""
1413 """get a file context from the working directory"""
1414 return self._filectxfn(self._repo, self, path)
1414 return self._filectxfn(self._repo, self, path)
1415
1415
1416 def commit(self):
1416 def commit(self):
1417 """commit context to the repo"""
1417 """commit context to the repo"""
1418 return self._repo.commitctx(self)
1418 return self._repo.commitctx(self)
1419
1419
1420 class memfilectx(object):
1420 class memfilectx(object):
1421 """memfilectx represents an in-memory file to commit.
1421 """memfilectx represents an in-memory file to commit.
1422
1422
1423 See memctx for more details.
1423 See memctx for more details.
1424 """
1424 """
1425 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1425 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1426 """
1426 """
1427 path is the normalized file path relative to repository root.
1427 path is the normalized file path relative to repository root.
1428 data is the file content as a string.
1428 data is the file content as a string.
1429 islink is True if the file is a symbolic link.
1429 islink is True if the file is a symbolic link.
1430 isexec is True if the file is executable.
1430 isexec is True if the file is executable.
1431 copied is the source file path if current file was copied in the
1431 copied is the source file path if current file was copied in the
1432 revision being committed, or None."""
1432 revision being committed, or None."""
1433 self._path = path
1433 self._path = path
1434 self._data = data
1434 self._data = data
1435 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1435 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1436 self._copied = None
1436 self._copied = None
1437 if copied:
1437 if copied:
1438 self._copied = (copied, nullid)
1438 self._copied = (copied, nullid)
1439
1439
1440 def __nonzero__(self):
1440 def __nonzero__(self):
1441 return True
1441 return True
1442 def __str__(self):
1442 def __str__(self):
1443 return "%s@%s" % (self.path(), self._changectx)
1443 return "%s@%s" % (self.path(), self._changectx)
1444 def path(self):
1444 def path(self):
1445 return self._path
1445 return self._path
1446 def data(self):
1446 def data(self):
1447 return self._data
1447 return self._data
1448 def flags(self):
1448 def flags(self):
1449 return self._flags
1449 return self._flags
1450 def isexec(self):
1450 def isexec(self):
1451 return 'x' in self._flags
1451 return 'x' in self._flags
1452 def islink(self):
1452 def islink(self):
1453 return 'l' in self._flags
1453 return 'l' in self._flags
1454 def renamed(self):
1454 def renamed(self):
1455 return self._copied
1455 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now