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