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