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