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