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