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