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